Django PCI Compliance
Bottom Line Up Front
If you’re processing, storing, or transmitting cardholder data in a Django application, you need to understand how Django’s security features map to PCI DSS requirements. The good news: Django provides many security controls out of the box that directly support your compliance efforts. The challenge: you’ll need to configure, harden, and validate these features specifically for PCI environments while addressing Django-unique considerations like its ORM, middleware stack, and template system.
This guide shows you exactly how to implement Django PCI compliance — from secure coding practices and database encryption to session management and audit logging. Whether you’re building a payment page, integrating with payment gateways, or storing tokenized card data, you’ll learn how to leverage Django’s security framework while meeting every applicable PCI requirement.
Technical Overview
How Django Security Supports PCI Compliance
Django’s security architecture provides several features that directly address PCI requirements:
- Secure session management with configurable session engines
- CSRF protection enabled by default on all POST requests
- SQL injection prevention through parameterized queries in the ORM
- XSS protection via automatic template escaping
- Clickjacking protection through X-Frame-Options headers
- SSL/TLS redirect capabilities for secure connections
- User authentication framework with password hashing and validation
Architecture Considerations
In PCI environments, Django applications typically follow one of these patterns:
Tokenization Architecture: Django handles tokenized values only, with actual card data processed by third-party payment processors. This significantly reduces PCI scope.
Isolated Payment Module: Django serves the main application while a separate, highly secured Django instance handles payment processing in an isolated network segment.
API Gateway Pattern: Django provides business logic while API gateways handle authentication, rate limiting, and request validation before traffic reaches your application.
Defense-in-Depth Placement
Django operates at the application layer but must integrate with your broader security stack:
- Web Application Firewall (WAF) in front of Django for additional input validation
- Reverse proxy (nginx/Apache) handling SSL termination and static files
- Database encryption at rest and in transit
- Network segmentation isolating Django instances that handle sensitive data
- Intrusion Detection Systems (IDS) monitoring Django application logs
Industry Standards Beyond PCI
Django security also aligns with:
- OWASP Top 10 protections built into the framework
- NIST Cybersecurity Framework controls for application security
- ISO 27001 requirements for secure development
- CIS Controls for web application security
PCI DSS Requirements Addressed
Core Requirements Met by Django
Requirement 2.2.3 (Implement security features for common services): Django’s security middleware provides essential protections when properly configured.
Requirement 3.4 (Render PAN unreadable): Django’s encryption capabilities support field-level encryption for stored card data.
Requirement 6.2 (Protect against known vulnerabilities): Django’s regular security releases and built-in protections address common web vulnerabilities.
Requirement 6.3 (Secure software development): Django’s secure-by-default design supports secure coding practices.
Requirement 6.5 (Common vulnerabilities): Django provides specific protections against OWASP Top 10 vulnerabilities:
- SQL injection (6.5.1)
- Buffer overflows (6.5.2)
- XSS (6.5.7)
- CSRF (6.5.9)
- Broken authentication (6.5.10)
Requirement 8.2 (User authentication): Django’s authentication system supports strong password policies and account lockout mechanisms.
Requirement 10.2 (Audit logging): Django’s logging framework can capture all required security events.
SAQ Type Considerations
| SAQ Type | Django Implementation Requirements |
|---|---|
| SAQ A | Minimal – redirect to payment page only |
| SAQ A-EP | Implement secure payment fields, no CHD storage |
| SAQ C | Full security controls, encrypted storage if handling CHD |
| SAQ D | Complete implementation of all applicable controls |
Compliance Thresholds
Meets Requirements: Default Django security settings with proper configuration
Exceeds Requirements: Add field-level encryption, advanced audit logging, real-time security monitoring
Implementation Guide
Step 1: Secure Django Settings
Configure your production settings for PCI compliance:
“`python
settings.py
import os
from django.core.management.utils import get_random_secret_key
Security fundamentals
SECRET_KEY = os.environ.get(‘DJANGO_SECRET_KEY’, get_random_secret_key())
DEBUG = False
ALLOWED_HOSTS = [‘your-domain.com’, ‘www.your-domain.com’]
Force HTTPS
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Session security
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = ‘Strict’
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 900 # 15 minutes for PCI
Additional security headers
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = ‘DENY’
Password validation (Req 8.2)
AUTH_PASSWORD_VALIDATORS = [
{
‘NAME’: ‘django.contrib.auth.password_validation.UserAttributeSimilarityValidator’,
},
{
‘NAME’: ‘django.contrib.auth.password_validation.MinimumLengthValidator’,
‘OPTIONS’: {
‘min_length’: 12, # PCI requires strong passwords
}
},
{
‘NAME’: ‘django.contrib.auth.password_validation.CommonPasswordValidator’,
},
{
‘NAME’: ‘django.contrib.auth.password_validation.NumericPasswordValidator’,
},
]
“`
Step 2: Implement Field-Level Encryption
For any stored sensitive data, implement field-level encryption:
“`python
models.py
from django.db import models
from django_cryptography.fields import encrypt
class PaymentToken(models.Model):
# Never store actual PANs – use tokens
token = encrypt(models.CharField(max_length=255))
last_four = models.CharField(max_length=4) # Safe to store unencrypted
expiry_date = encrypt(models.CharField(max_length=5))
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = ‘payment_tokens’
indexes = [
models.Index(fields=[‘last_four’]),
]
“`
Step 3: Configure Audit Logging
Implement comprehensive logging for all access to sensitive data:
“`python
logging_config.py
LOGGING = {
‘version’: 1,
‘disable_existing_loggers’: False,
‘formatters’: {
‘pci’: {
‘format’: ‘{levelname} {asctime} {module} {process:d} {thread:d} {message}’,
‘style’: ‘{‘,
},
},
‘handlers’: {
‘pci_file’: {
‘level’: ‘INFO’,
‘class’: ‘logging.handlers.RotatingFileHandler’,
‘filename’: ‘/var/log/django/pci_audit.log’,
‘maxBytes’: 1024 1024 100, # 100 MB
‘backupCount’: 10,
‘formatter’: ‘pci’,
},
‘security’: {
‘level’: ‘WARNING’,
‘class’: ‘logging.handlers.RotatingFileHandler’,
‘filename’: ‘/var/log/django/security.log’,
‘maxBytes’: 1024 1024 100,
‘backupCount’: 10,
‘formatter’: ‘pci’,
},
},
‘loggers’: {
‘pci_audit’: {
‘handlers’: [‘pci_file’],
‘level’: ‘INFO’,
‘propagate’: False,
},
‘django.security’: {
‘handlers’: [‘security’],
‘level’: ‘WARNING’,
‘propagate’: True,
},
},
}
audit_middleware.py
import logging
from django.utils import timezone
logger = logging.getLogger(‘pci_audit’)
class PciAuditMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Log all access to payment-related endpoints
if ‘/payment/’ in request.path or ‘/api/payment/’ in request.path:
logger.info(
f”Payment access – User: {request.user.id if request.user.is_authenticated else ‘Anonymous’}, ”
f”IP: {self.get_client_ip(request)}, Path: {request.path}, ”
f”Method: {request.method}, Time: {timezone.now()}”
)
response = self.get_response(request)
return response
def get_client_ip(self, request):
x_forwarded_for = request.META.get(‘HTTP_X_FORWARDED_FOR’)
if x_forwarded_for:
ip = x_forwarded_for.split(‘,’)[0]
else:
ip = request.META.get(‘REMOTE_ADDR’)
return ip
“`
Step 4: Secure Database Connections
Configure encrypted database connections:
“`python
settings.py
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.postgresql’,
‘NAME’: os.environ.get(‘DB_NAME’),
‘USER’: os.environ.get(‘DB_USER’),
‘PASSWORD’: os.environ.get(‘DB_PASSWORD’),
‘HOST’: os.environ.get(‘DB_HOST’),
‘PORT’: ‘5432’,
‘OPTIONS’: {
‘sslmode’: ‘require’,
‘sslcert’: ‘/path/to/client-cert.pem’,
‘sslkey’: ‘/path/to/client-key.pem’,
‘sslrootcert’: ‘/path/to/ca-cert.pem’,
},
}
}
“`
Step 5: Implement Input Validation
Create validators for all payment-related inputs:
“`python
validators.py
from django.core.exceptions import ValidationError
import re
def validate_card_number(value):
“””Validate card number format (for display purposes only)”””
# Remove spaces and dashes
value = re.sub(r'[s-]’, ”, value)
# Check if it’s numeric and correct length
if not value.isdigit() or len(value) not in [13, 14, 15, 16]:
raise ValidationError(‘Invalid card number format’)
# Never store this value – only use for immediate processing
def validate_cvv(value):
“””Validate CVV format”””
if not value.isdigit() or len(value) not in [3, 4]:
raise ValidationError(‘Invalid CVV format’)
# Never store CVV under any circumstances
forms.py
from django import forms
class PaymentForm(forms.Form):
# These fields should only be used for immediate processing
# Never save to database
card_number = forms.CharField(
max_length=19,
validators=[validate_card_number],
widget=forms.TextInput(attrs={‘autocomplete’: ‘off’})
)
cvv = forms.CharField(
max_length=4,
validators=[validate_cvv],
widget=forms.TextInput(attrs={‘autocomplete’: ‘off’})
)
def clean(self):
cleaned_data = super().clean()
# Add any cross-field validation
return cleaned_data
“`
Step 6: Configure WAF Rules
If using AWS WAF or similar, configure Django-specific rules:
“`json
{
“Name”: “DjangoSecurityRules”,
“Rules”: [
{
“Name”: “BlockSQLInjection”,
“Priority”: 1,
“Statement”: {
“SqliMatchStatement”: {
“FieldToMatch”: {
“AllQueryArguments”: {}
},
“TextTransformations”: [
{
“Priority”: 0,
“Type”: “URL_DECODE”
},
{
“Priority”: 1,
“Type”: “HTML_ENTITY_DECODE”
}
]
}
}
},
{
“Name”: “RateLimitPaymentAPI”,
“Priority”: 2,
“Statement”: {
“RateBasedStatement”: {
“Limit”: 100,
“AggregateKeyType”: “IP”
}
}
}
]
}
“`
Testing and Validation
Automated Security Testing
Implement continuous security testing in your CI/CD pipeline:
“`python
tests/test_security.py
from django.test import TestCase, Client
from django.contrib.auth.models import User
import ssl
class SecurityHeadersTest(TestCase):
def setUp(self):
self.client = Client()
def test_security_headers(self):
response = self.client.get(‘/’)
# Verify security headers
self.assertEqual(response[‘X-Frame-Options’], ‘DENY’)
self.assertEqual(response[‘X-Content-Type-Options’], ‘nosniff’)
self.assertTrue(‘strict-transport-security’ in response)
def test_ssl_redirect(self):
response = self.client.get(‘/’, secure=False)
self.assertEqual(response.status_code, 301)
def test_session_security(self):
response = self.client.get(‘/’)
self.assertTrue(response.cookies[‘sessionid’][‘secure’])
self.assertTrue(response.cookies[‘sessionid’][‘httponly’])
class AuthenticationTest(TestCase):
def test_password_requirements(self):
# Test weak password rejection
user = User(username=’testuser’)
with self.assertRaises(ValidationError):
user.set_password(‘weak’)
user.full_clean()
def test_account_lockout(self):
# Implement account lockout testing
pass
“`
Manual Validation Checklist
Configuration Review:
- [ ] All security settings enabled in production
- [ ] Database connections use SSL/TLS
- [ ] Secret keys stored securely (environment variables)
- [ ] Debug mode disabled
Code Review:
- [ ] No hardcoded credentials
- [ ] All user inputs validated
- [ ] Sensitive data encrypted at rest
- [ ] SQL queries use ORM or parameterized queries
Penetration Testing Focus Areas:
- Authentication bypass attempts
- Session fixation vulnerabilities
- SQL injection through search/filter functions
- XSS in user-generated content areas
- CSRF token validation
Evidence Collection
Document these items for your compliance file:
“`bash
Generate security configuration report
python manage.py check –deploy > security_config_report.txt
Export middleware configuration
python manage.py shell -c “from django.conf import settings; print(settings.MIDDLEWARE)” > middleware_config.txt
Document installed packages and versions
pip freeze > requirements_audit.txt
“`
Operational Maintenance
Daily Tasks
Log Review: Check security and audit logs for anomalies
“`bash
Check for authentication failures
grep “Failed login” /var/log/django/security.log | tail -50
Monitor for suspicious patterns
grep “payment” /var/log/django/pci_audit.log | awk ‘{print $7}’ | sort | uniq -c | sort -nr
“`
Weekly Tasks
Vulnerability Scanning: Run automated security scans
“`bash
Use Django’s built-in security check
python