B04 Secure Design
# Example: Input Validation
# ------------------------------------------------------------------------------
# Validates user input before using it. This prevents unexpected behaviour
# or vulnerabilities such as injection or crashes.
import re
user_input = input('Enter an age (0-120): ')
# Accept only digits using a regular expression
if not re.fullmatch(r"\d+", user_input):
raise ValueError('Input must be a positive integer')
age = int(user_input)
if not 0 <= age <= 120:
raise ValueError('Age out of expected range')
print(f'Your age is {age}')
Bad Password Storage
# Password Storage - Bad Example
# ------------------------------------------------------------------------------
# Storing or hashing passwords without a salt and a strong hash function makes
# them easy to crack if the database is compromised.
import hashlib
password = input('Enter password: ')
# BAD: Unsalted, single round MD5 hash
hash_value = hashlib.md5(password.encode('utf-8')).hexdigest()
print('Storing password hash:', hash_value)
Good Password Storage
# Password Storage - Good Example
# ------------------------------------------------------------------------------
# Use a strong key derivation function with a unique salt for each password.
# PBKDF2 is built into Python's hashlib module.
import os
import hashlib
import base64
password = input('Enter password: ')
salt = os.urandom(16)
hash_bytes = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100_000)
encoded_salt = base64.b64encode(salt).decode('utf-8')
encoded_hash = base64.b64encode(hash_bytes).decode('utf-8')
print('Storing password hash:')
print(f'salt={encoded_salt}')
print(f'hash={encoded_hash}')
Bad SQL Injection
# SQL Injection - Bad Example
# ------------------------------------------------------------------------------
# Directly formatting user input into an SQL query can allow injection attacks.
# NEVER build queries using string concatenation with untrusted data.
import sqlite3
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
cursor.execute('CREATE TABLE users(id INTEGER PRIMARY KEY, username TEXT)')
username = input('Enter username to lookup: ')
# BAD: user input is concatenated directly into the query
query = f"SELECT * FROM users WHERE username = '{username}'"
print('Executing:', query)
for row in cursor.execute(query):
print(row)
Good SQL Injection
# SQL Injection - Good Example
# ------------------------------------------------------------------------------
# Parameterized queries separate data from code, preventing SQL injection.
# Use parameter placeholders and pass parameters as a tuple.
import sqlite3
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
cursor.execute('CREATE TABLE users(id INTEGER PRIMARY KEY, username TEXT)')
username = input('Enter username to lookup: ')
# GOOD: query uses ? placeholders and parameters tuple
query = 'SELECT * FROM users WHERE username = ?'
print('Executing:', query, 'with params', username)
for row in cursor.execute(query, (username,)):
print(row)
Secret From Env
# Secret Management
# ------------------------------------------------------------------------------
# Retrieve secrets like API keys from environment variables instead of
# hardcoding them in the source code.
import os
api_key = os.getenv('API_KEY')
if api_key is None:
raise RuntimeError('API_KEY environment variable is not set')
print('Loaded API key of length', len(api_key))