Django PCI Compliance

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

Leave a Comment

icon 1,650 PCI scans performed this month
check icon Business in Austin, TX completed their PCI SAQ A-EP