422.46 File and hardware protection
Protect user data and physical devices by controlling how files are accessed, stored, and handled during execution.
422.46 File and Hardware Protection
Protect user data and physical devices by controlling how files are accessed, stored, and handled during execution.
Overview
Software that handles files or interacts with physical devices must be written with care. Unvalidated file input, insecure file storage, or unauthorised hardware access can lead to serious security breaches. Additionally, side-channel attacks can reveal sensitive information by observing how software interacts with the underlying system.
This topic focuses on how developers can reduce these risks through secure file handling and defensive coding patterns that limit exposure to hardware-based vulnerabilities in Flask applications.
Learning Targets
In this topic, students learn to:
Identify common file and hardware vulnerabilities
Apply safe file handling and access controls in Flask applications
Design programs that minimise exposure to physical attack vectors
Understand the risks of side-channel attacks and how to prevent them
File Security Vulnerabilities
File handling in web applications creates multiple security risks that attackers can exploit to access unauthorised data or execute malicious code.
Dangerous File Upload Example
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
# VULNERABLE: No validation of uploaded files
file = request.files['file']
filename = file.filename
# DANGEROUS: Direct use of user-provided filename
file.save(f'uploads/{filename}')
return f"File {filename} uploaded successfully"
# Attack examples:
# - filename = "../../app.py" (overwrite application code)
# - filename = "malicious.php" (upload executable code)
# - filename = "virus.exe" (upload malware)
Secure File Upload Implementation
import os
from werkzeug.utils import secure_filename
# Define allowed file types
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
def allowed_file(filename):
"""Check if file type is allowed"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def get_file_size(file):
"""Get file size safely"""
file.seek(0, os.SEEK_END)
size = file.tell()
file.seek(0) # Reset file pointer
return size
@app.route('/upload_secure', methods=['POST'])
def upload_file_secure():
try:
# Check if file was uploaded
if 'file' not in request.files:
return "No file provided", 400
file = request.files['file']
# Check if filename is provided
if file.filename == '':
return "No file selected", 400
# Validate file type
if not allowed_file(file.filename):
return "File type not allowed. Only txt, pdf, png, jpg, jpeg, gif files are permitted", 400
# Check file size
file_size = get_file_size(file)
if file_size > MAX_FILE_SIZE:
return f"File too large. Maximum size is {MAX_FILE_SIZE // (1024*1024)}MB", 400
# Secure the filename
filename = secure_filename(file.filename)
# Add timestamp to prevent filename conflicts
import time
timestamp = str(int(time.time()))
name, ext = os.path.splitext(filename)
safe_filename = f"{name}_{timestamp}{ext}"
# Ensure upload directory exists
upload_dir = 'uploads'
os.makedirs(upload_dir, exist_ok=True)
# Save file safely
file_path = os.path.join(upload_dir, safe_filename)
file.save(file_path)
# Set restrictive file permissions (Unix/Linux)
os.chmod(file_path, 0o644) # Read/write for owner, read for others
return f"File uploaded successfully as {safe_filename}"
except Exception as e:
app.logger.error(f"File upload error: {e}")
return "Upload failed. Please try again.", 500
# Benefits of this approach:
# 1. Only allowed file types accepted
# 2. Filenames sanitised to prevent path traversal
# 3. File size limits prevent DoS attacks
# 4. Timestamps prevent filename conflicts
# 5. Restrictive file permissions
Path Traversal Prevention
@app.route('/download/<filename>')
def download_file_vulnerable(filename):
# VULNERABLE: Direct file access
return send_file(f'uploads/{filename}')
# Attack: /download/../../../etc/passwd
# Could access system files outside upload directory
@app.route('/download_secure/<filename>')
def download_file_secure(filename):
# SECURE: Validate and restrict file access
# Basic filename validation
if not filename or '..' in filename or filename.startswith('/'):
return "Invalid filename", 400
# Ensure filename is safe
safe_filename = secure_filename(filename)
if safe_filename != filename:
return "Invalid filename", 400
# Construct full path
upload_dir = os.path.abspath('uploads')
file_path = os.path.join(upload_dir, safe_filename)
# Ensure file is within upload directory
if not file_path.startswith(upload_dir):
return "Access denied", 403
# Check if file exists
if not os.path.isfile(file_path):
return "File not found", 404
return send_file(file_path, as_attachment=True)
File Content Validation
def validate_image_file(file_path):
"""
Validate that uploaded image file is actually an image
"""
try:
from PIL import Image
# Try to open as image
with Image.open(file_path) as img:
# Verify it's a valid image format
img.verify()
return True
except Exception:
return False
def scan_file_content(file_path):
"""
Basic content scanning for dangerous patterns
"""
dangerous_patterns = [
b'<script', # JavaScript
b'<?php', # PHP code
b'exec(', # Code execution
b'system(', # System commands
b'shell_exec(', # Shell execution
]
try:
with open(file_path, 'rb') as f:
content = f.read(1024) # Read first 1KB
for pattern in dangerous_patterns:
if pattern in content.lower():
return False
return True
except Exception:
return False
@app.route('/upload_validated', methods=['POST'])
def upload_with_validation():
file = request.files['file']
# Basic validation (as before)
if not allowed_file(file.filename):
return "Invalid file type", 400
# Save temporarily for content validation
temp_path = f'temp_{secure_filename(file.filename)}'
file.save(temp_path)
try:
# Validate file content based on type
if file.filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')):
if not validate_image_file(temp_path):
return "Invalid image file", 400
# Scan for dangerous content
if not scan_file_content(temp_path):
return "File contains potentially dangerous content", 400
# Move to final location if validation passes
final_path = f'uploads/{secure_filename(file.filename)}'
os.rename(temp_path, final_path)
return "File uploaded and validated successfully"
finally:
# Clean up temporary file
if os.path.exists(temp_path):
os.remove(temp_path)
Side-Channel Attack Prevention
Side-channel attacks exploit indirect information leaked by a program's execution, such as timing differences or resource usage patterns.
Timing Attack Prevention
import time
import hmac
def vulnerable_password_check(stored_password, provided_password):
"""
VULNERABLE: Timing attack possible
"""
# This comparison stops at first different character
# Attacker can measure response time to guess password character by character
if stored_password == provided_password:
return True
return False
def secure_password_check(stored_password, provided_password):
"""
SECURE: Constant-time comparison
"""
# hmac.compare_digest performs constant-time comparison
return hmac.compare_digest(stored_password, provided_password)
@app.route('/login', methods=['POST'])
def login_timing_safe():
username = request.form['username']
password = request.form['password']
# Get stored password hash
stored_hash = get_user_password_hash(username)
if stored_hash:
# Use timing-safe comparison
if secure_password_check(stored_hash, hash_password(password)):
session['user_id'] = get_user_id(username)
return "Login successful"
# Add consistent delay regardless of success/failure
time.sleep(0.1) # Prevents timing analysis of user enumeration
return "Invalid credentials"
Information Leakage Prevention
@app.route('/user/<username>')
def get_user_profile(username):
"""
Example of preventing information leakage
"""
try:
user = get_user_by_username(username)
if not user:
# Don't reveal whether username exists
return "User not found", 404
# Check if current user can view this profile
if not can_view_profile(session.get('user_id'), user['id']):
# Same response as non-existent user
return "User not found", 404
return render_template('profile.html', user=user)
except Exception as e:
# Don't leak error details
app.logger.error(f"Profile access error: {e}")
return "User not found", 404
def can_view_profile(viewer_id, profile_user_id):
"""Check if user can view another user's profile"""
if not viewer_id:
return False
# Users can always view their own profile
if viewer_id == profile_user_id:
return True
# Check if profile is public
return is_profile_public(profile_user_id)
Secure File Storage Practices
import json
import os
from cryptography.fernet import Fernet
class SecureFileStorage:
"""
Secure file storage with encryption
"""
def __init__(self, storage_dir='secure_storage'):
self.storage_dir = storage_dir
os.makedirs(storage_dir, exist_ok=True)
# Generate or load encryption key
self.key = self.get_or_create_key()
self.cipher = Fernet(self.key)
def get_or_create_key(self):
"""Get existing key or create new one"""
key_file = 'file_encryption.key'
if os.path.exists(key_file):
with open(key_file, 'rb') as f:
return f.read()
else:
# Generate new key
key = Fernet.generate_key()
with open(key_file, 'wb') as f:
f.write(key)
# Set restrictive permissions on key file
os.chmod(key_file, 0o600) # Owner read/write only
return key
def store_secure_data(self, data, filename):
"""Store data with encryption"""
try:
# Convert to bytes if necessary
if isinstance(data, str):
data = data.encode('utf-8')
elif isinstance(data, dict):
data = json.dumps(data).encode('utf-8')
# Encrypt data
encrypted_data = self.cipher.encrypt(data)
# Store in secure directory
file_path = os.path.join(self.storage_dir, secure_filename(filename))
with open(file_path, 'wb') as f:
f.write(encrypted_data)
# Set restrictive permissions
os.chmod(file_path, 0o600)
return True
except Exception as e:
app.logger.error(f"Secure storage error: {e}")
return False
def retrieve_secure_data(self, filename):
"""Retrieve and decrypt data"""
try:
file_path = os.path.join(self.storage_dir, secure_filename(filename))
if not os.path.exists(file_path):
return None
with open(file_path, 'rb') as f:
encrypted_data = f.read()
# Decrypt data
decrypted_data = self.cipher.decrypt(encrypted_data)
return decrypted_data.decode('utf-8')
except Exception as e:
app.logger.error(f"Secure retrieval error: {e}")
return None
# Usage example
secure_storage = SecureFileStorage()
@app.route('/store_sensitive', methods=['POST'])
def store_sensitive_data():
if 'user_id' not in session:
return "Unauthorised", 401
sensitive_data = request.form['data']
user_id = session['user_id']
# Store encrypted data
filename = f"user_{user_id}_data.enc"
if secure_storage.store_secure_data(sensitive_data, filename):
return "Data stored securely"
else:
return "Storage failed", 500
Hardware and System Protection
import psutil
import os
def check_system_resources():
"""
Monitor system resources to prevent abuse
"""
# Check memory usage
memory = psutil.virtual_memory()
if memory.percent > 90:
return False, "High memory usage"
# Check disk space
disk = psutil.disk_usage('/')
if disk.percent > 95:
return False, "Low disk space"
# Check CPU usage
cpu_percent = psutil.cpu_percent(interval=1)
if cpu_percent > 95:
return False, "High CPU usage"
return True, "Resources OK"
@app.before_request
def resource_check():
"""Check system resources before processing requests"""
if request.endpoint in ['upload', 'process_data']:
is_ok, message = check_system_resources()
if not is_ok:
app.logger.warning(f"Resource check failed: {message}")
return "Service temporarily unavailable", 503
def secure_temp_file_handling():
"""
Secure temporary file creation and cleanup
"""
import tempfile
# Create temporary file in secure location
temp_fd, temp_path = tempfile.mkstemp(
suffix='.tmp',
prefix='secure_',
dir='/tmp' # Or app-specific temp directory
)
try:
# Set restrictive permissions
os.chmod(temp_path, 0o600)
# Use the temporary file
with os.fdopen(temp_fd, 'w') as temp_file:
temp_file.write("Sensitive temporary data")
# Process the file...
finally:
# Always clean up temporary files
try:
os.unlink(temp_path)
except OSError:
pass # File already deleted
Code Interpretation Examples
# Example 1: Identify file security issue
@app.route('/view_file')
def view_file():
filename = request.args.get('file')
with open(filename, 'r') as f:
return f.read()
# Problems:
# 1. No path validation - can access any file
# 2. No access control
# 3. Could expose system files
# Example 2: Timing attack vulnerability
def check_api_key(provided_key):
correct_key = "secret123"
for i in range(len(correct_key)):
if provided_key[i] != correct_key[i]:
return False
return True
# Problem: Returns False as soon as wrong character found
# Timing difference reveals information about correct key
# Example 3: Secure file validation
@app.route('/upload', methods=['POST'])
def secure_upload():
file = request.files.get('file')
# Comprehensive validation
if not file or file.filename == '':
return "No file provided", 400
# Check file type
if not file.filename.endswith(('.txt', '.pdf')):
return "Invalid file type", 400
# Check file size
if len(file.read()) > 1024 * 1024: # 1MB
return "File too large", 400
file.seek(0) # Reset file pointer
# Secure filename and save
safe_name = secure_filename(file.filename)
file.save(f'uploads/{safe_name}')
return "File uploaded successfully"
Summary
File upload security requires validation of type, size, content, and filename sanitisation
Path traversal prevention ensures files are accessed only from intended directories
Side-channel attacks can be prevented through constant-time operations and by avoiding information leakage
Secure file storage may require encryption for sensitive data
System resource monitoring prevents abuse and denial-of-service attacks
Temporary file handling must include proper cleanup and secure permissions
File and hardware protection requires a comprehensive approach that considers both the immediate security of file operations and the broader system implications. By implementing proper validation, access controls, and monitoring, Flask applications can safely handle file operations while protecting against various attack vectors.
Last updated
Was this helpful?