B04 Secure Design

Input Validation

# 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))