# Vulnerable: os.path.join() with user input in Django
import os
from django.http import HttpResponse, JsonResponse
from django.views import View
from django.conf import settings
# Dangerous: Direct path joining with user input
class FileDownloadView(View):
def get(self, request, filename):
# CRITICAL: User controls filename, can use ../
file_path = os.path.join(settings.MEDIA_ROOT, 'uploads', filename)
try:
with open(file_path, 'rb') as f:
response = HttpResponse(f.read())
response['Content-Disposition'] = f'attachment; filename={filename}'
return response
except FileNotFoundError:
return JsonResponse({'error': 'File not found'}, status=404)
# Another vulnerable pattern
def serve_user_file(request):
user_id = request.GET.get('user_id', '')
filename = request.GET.get('file', '')
# Dangerous: Multiple path components from user input
file_path = os.path.join(settings.MEDIA_ROOT, 'users', user_id, filename)
if os.path.exists(file_path):
with open(file_path, 'r') as f:
content = f.read()
return HttpResponse(content)
return HttpResponse('File not found', status=404)
# Template file access
def load_custom_template(request):
template_name = request.POST.get('template', '')
template_dir = request.POST.get('directory', 'default')
# Dangerous: User controls template path
template_path = os.path.join(settings.TEMPLATES[0]['DIRS'][0], template_dir, template_name)
try:
with open(template_path, 'r') as f:
template_content = f.read()
return JsonResponse({'template': template_content})
except Exception as e:
return JsonResponse({'error': str(e)})
# Log file access
def view_log_file(request):
log_name = request.GET.get('log', '')
date = request.GET.get('date', '')
# Dangerous: Path traversal in log access
log_path = os.path.join('/var/log/myapp', date, f'{log_name}.log')
try:
with open(log_path, 'r') as f:
log_content = f.read()
return HttpResponse(log_content, content_type='text/plain')
except Exception as e:
return HttpResponse(f'Error: {e}', status=500)
# Configuration file access
def get_config_file(request):
config_name = request.GET.get('config', '')
environment = request.GET.get('env', 'production')
# Dangerous: User-controlled config path
config_path = os.path.join(settings.BASE_DIR, 'config', environment, f'{config_name}.conf')
if os.path.exists(config_path):
with open(config_path, 'r') as f:
config_data = f.read()
return JsonResponse({'config': config_data})
return JsonResponse({'error': 'Config not found'})
# Secure: Safe path handling in Django
import os
from pathlib import Path
from django.http import HttpResponse, JsonResponse
from django.views import View
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage
import re
# Safe: Validated file download
class SafeFileDownloadView(View):
def get(self, request, filename):
try:
# Validate filename
validated_filename = self.validate_filename(filename)
# Construct safe file path
safe_path = self.get_safe_file_path(validated_filename)
# Serve file securely
return self.serve_file_safely(safe_path, validated_filename)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_filename(self, filename):
# Check filename format
if not filename or len(filename) > 255:
raise ValidationError('Invalid filename length')
# Only allow safe characters
if not re.match(r'^[a-zA-Z0-9._-]+$', filename):
raise ValidationError('Filename contains invalid characters')
# Prevent hidden files
if filename.startswith('.'):
raise ValidationError('Hidden files not allowed')
# Check file extension
allowed_extensions = ['.txt', '.pdf', '.jpg', '.png', '.docx']
if not any(filename.lower().endswith(ext) for ext in allowed_extensions):
raise ValidationError('File type not allowed')
return filename
def get_safe_file_path(self, filename):
# Define safe base directory
upload_dir = Path(settings.MEDIA_ROOT) / 'uploads'
# Construct absolute path
file_path = upload_dir / filename
# Resolve path and validate it's within upload directory
try:
resolved_path = file_path.resolve()
upload_dir_resolved = upload_dir.resolve()
# Ensure file is within the upload directory
resolved_path.relative_to(upload_dir_resolved)
return resolved_path
except ValueError:
raise ValidationError('File path outside allowed directory')
def serve_file_safely(self, file_path, filename):
try:
if not file_path.exists():
return JsonResponse({'error': 'File not found'}, status=404)
# Check file size
max_size = 10 * 1024 * 1024 # 10MB
if file_path.stat().st_size > max_size:
return JsonResponse({'error': 'File too large'}, status=413)
# Serve file
with open(file_path, 'rb') as f:
response = HttpResponse(f.read())
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
except PermissionError:
return JsonResponse({'error': 'Access denied'}, status=403)
except Exception:
return JsonResponse({'error': 'File read error'}, status=500)
# Safe: User file access with validation
def safe_serve_user_file(request):
user_id = request.GET.get('user_id', '')
filename = request.GET.get('file', '')
try:
# Validate user access
validated_user_id = validate_user_access(request, user_id)
# Validate filename
validated_filename = validate_user_filename(filename)
# Get safe file path
file_path = get_safe_user_file_path(validated_user_id, validated_filename)
# Serve file
return serve_user_file_safely(file_path)
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_user_access(request, user_id):
# Validate user ID format
if not user_id.isdigit():
raise ValidationError('Invalid user ID')
user_id = int(user_id)
# Check access permissions
if not request.user.is_authenticated:
raise ValidationError('Authentication required')
if request.user.id != user_id and not request.user.is_staff:
raise ValidationError('Access denied')
return user_id
def validate_user_filename(filename):
if not filename or len(filename) > 100:
raise ValidationError('Invalid filename')
# Only allow alphanumeric, dots, hyphens, underscores
if not re.match(r'^[a-zA-Z0-9._-]+$', filename):
raise ValidationError('Filename contains invalid characters')
# Prevent directory traversal sequences
if '..' in filename or '/' in filename or '\\' in filename:
raise ValidationError('Invalid file path')
return filename
def get_safe_user_file_path(user_id, filename):
# Define safe base directory
users_dir = Path(settings.MEDIA_ROOT) / 'users' / str(user_id)
# Construct file path
file_path = users_dir / filename
# Validate path is within user directory
try:
resolved_path = file_path.resolve()
users_dir_resolved = users_dir.resolve()
resolved_path.relative_to(users_dir_resolved)
return resolved_path
except ValueError:
raise ValidationError('File path outside user directory')
def serve_user_file_safely(file_path):
try:
if not file_path.exists():
return HttpResponse('File not found', status=404)
# Read file with size limit
max_size = 1024 * 1024 # 1MB
if file_path.stat().st_size > max_size:
return HttpResponse('File too large', status=413)
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return HttpResponse(content)
except UnicodeDecodeError:
return HttpResponse('File encoding error', status=400)
except Exception:
return HttpResponse('File read error', status=500)
# Safe: Template access with allowlists
def safe_load_custom_template(request):
template_name = request.POST.get('template', '')
template_category = request.POST.get('category', '')
try:
# Validate inputs
validated_template = validate_template_request(template_name, template_category)
# Load template safely
template_content = load_template_safely(validated_template)
return JsonResponse({'template': template_content})
except ValidationError as e:
return JsonResponse({'error': str(e)}, status=400)
def validate_template_request(template_name, category):
# Validate template name
if not re.match(r'^[a-zA-Z0-9_-]+$', template_name):
raise ValidationError('Invalid template name')
# Validate category
allowed_categories = ['emails', 'reports', 'notifications']
if category not in allowed_categories:
raise ValidationError('Template category not allowed')
# Use allowlist for template names
allowed_templates = {
'emails': ['welcome', 'password_reset', 'confirmation'],
'reports': ['monthly', 'quarterly', 'annual'],
'notifications': ['alert', 'reminder', 'update']
}
if template_name not in allowed_templates.get(category, []):
raise ValidationError('Template not allowed')
return {'name': template_name, 'category': category}
def load_template_safely(template_data):
template_name = template_data['name']
category = template_data['category']
# Construct safe template path
template_dir = Path(settings.TEMPLATES[0]['DIRS'][0]) / 'custom' / category
template_path = template_dir / f'{template_name}.html'
# Validate path is within template directory
try:
resolved_path = template_path.resolve()
template_dir_resolved = template_dir.resolve()
resolved_path.relative_to(template_dir_resolved)
except ValueError:
raise ValidationError('Template path outside allowed directory')
# Read template
try:
if not resolved_path.exists():
raise ValidationError('Template not found')
with open(resolved_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception:
raise ValidationError('Template read error')
# Safe: Log access with restrictions
def safe_view_log_file(request):
log_name = request.GET.get('log', '')
try:
# Validate log access
if not request.user.is_staff:
raise ValidationError('Access denied')
# Validate log name
validated_log = validate_log_request(log_name)
# Get log content
log_content = get_log_content_safely(validated_log)
return HttpResponse(log_content, content_type='text/plain')
except ValidationError as e:
return HttpResponse(f'Error: {e}', status=400)
def validate_log_request(log_name):
# Only allow specific log names
allowed_logs = ['application', 'error', 'access', 'security']
if log_name not in allowed_logs:
raise ValidationError('Log file not allowed')
return log_name
def get_log_content_safely(log_name):
# Define safe log directory
log_dir = Path(settings.BASE_DIR) / 'logs'
log_path = log_dir / f'{log_name}.log'
# Validate path
try:
resolved_path = log_path.resolve()
log_dir_resolved = log_dir.resolve()
resolved_path.relative_to(log_dir_resolved)
except ValueError:
raise ValidationError('Log path outside allowed directory')
# Read log with size limit
try:
if not resolved_path.exists():
raise ValidationError('Log file not found')
max_size = 1024 * 1024 # 1MB
if resolved_path.stat().st_size > max_size:
# Read last 1MB
with open(resolved_path, 'rb') as f:
f.seek(-max_size, 2)
content = f.read().decode('utf-8', errors='ignore')
return '... (truncated)\n' + content
with open(resolved_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception:
raise ValidationError('Log read error')