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
- Physical Security: Data center access controls, locked server rooms
- Network Security: Firewalls, network segmentation, IDS/IPS
- Host Security: OS hardening, antivirus, patch management
- Application Security: Input validation, authentication, authorization
- 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
- Define the system: Create diagrams showing components, data flows, trust boundaries
- Identify threats: Use STRIDE or other frameworks to find potential threats
- Determine countermeasures: Decide how to address each threat
- 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 Type | Threat | Countermeasure |
|---|---|---|
| Spoofing | Attacker impersonates legitimate user | Multi-factor authentication, strong password policy |
| Tampering | Attacker modifies session cookie | Sign cookies, use secure flags, HTTPS only |
| Repudiation | User denies performing action | Audit logs with timestamps and IP addresses |
| Information Disclosure | Password exposed in transit | HTTPS encryption, never log passwords |
| Denial of Service | Brute force login attempts | Rate limiting, account lockout, CAPTCHA |
| Elevation of Privilege | Attacker gains admin access | Role-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
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
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)
- Defense in Depth: List at least 5 layers of defense for a web application that handles payment information.
Intermediate Exercises
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
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
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
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
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?
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:
CIA Triad: Every security decision should consider Confidentiality, Integrity, and Availability. These three pillars ensure that data is protected, accurate, and accessible.
Defense in Depth: Never rely on a single security control. Multiple layers of defense ensure that if one fails, others still protect the system.
Least Privilege: Grant only the minimum permissions necessary. This applies to users, applications, services, and processes.
Threat Modeling: Proactively identify threats using frameworks like STRIDE and design countermeasures before threats become real attacks.
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.