Django security best practices - protecting against common web security vulnerabilities | Daoman PythonAI

#django security best practices - protecting against common web security vulnerabilities

📂 Stage: Part 3 - Advanced Topics 🎯 Difficulty Level: Advanced ⏰ Estimated study time: 3-4 hours 🎒 Prerequisite knowledge: 用户认证系统

Table of contents


Security Overview

Web security is a red line that cannot be touched. Fortunately, Django provides powerful built-in protection, but the premise is that we must configure and use it correctly. This section will help you quickly understand the types of attacks that require the most attention and how Django responds to them.

  • Injection attack: django ORM prevents SQL injection, template automatic escaping prevents XSS
  • Broken Access Control: Control access through decorators and permissions system
  • Authentication failed: Built-in authentication middleware and password validator protect you
  • Security Configuration Error: Built-incheck --deployFind configuration issues with one click
  • CSRF: Native CSRF middleware directly prevents cross-site request forgery

Core Security Principles

  1. Minimum permissions: Only give users and processes the most necessary permissions to avoid using super users at will.
  2. Input whitelist, output fully escaped: Do not use fuzzy filtering instead of whitelist, all data output to the page must be escaped
  3. Defense in Depth: Layers of defense, even if one layer is breached, there is still protection behind it
  4. Do not expose sensitive information: It is prohibited to disclose sensitive data such as keys and paths in error pages and logs.

XSS protection

Cross-site scripting (XSS) is one of the most common web attacks. Simply put, the attacker injects malicious scripts into the page, and the scripts are executed when other users access the page, thereby stealing information or impersonating identities.

1. Template automatic escaping - the first line of defense

The django template engine will escape all HTML special characters by default.<become&lt;, so the script will not be executed. The only thing we have to do is not to disable this protection.

incorrect usage

Feel free to use|safeThe filter is opening the door to thieves:

<!-- 危险!不要这样 -->
<div>{{ user_comment|safe }}</div>
Correct usage

Keep auto-escaping, or explicitly useescapeFilter to make the output safe:

<!-- 安全:自动转义 -->
<div>{{ user_comment }}</div>

<!-- 显式转义(效果相同) -->
<div>{{ user_comment|escape }}</div>

2. How to deal with rich text? use bleach

Some scenarios do need to receive rich text input by users. For example, comments can contain bold fonts, line breaks, etc. You can’t simply let go at this timesafe, instead you should use a specialized cleaning library, such asbleach

Install bleach:

pip install bleach

Create a custom template filter to only allow safe tags and attributes:

# myapp/templatetags/safe_tags.py
import bleach
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter
def safe_html(text):
    allowed_tags = ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'code', 'pre']
    allowed_attrs = {'code': ['class']}
    cleaned = bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs, styles=[], strip=True)
    return mark_safe(cleaned)

When used in a template, just load the filter and call it:

{% load safe_tags %}
<div>{{ user_comment|safe_html }}</div>

3. Content Security Policy (CSP)

CSP is an additional line of defense executed by the browser. It tells the browser through HTTP response headers: which sources of resources can be loaded and which scripts can be executed. Django has some built-in support.

The most basic configuration is to enableSecurityMiddlewareAnd turn on several security headers:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',  # django内置,一键配置部分安全头
    # 其他中间件...
]

# 一键启用部分安全头
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True

A more complete CSP policy can be configured through custom middleware or at the reverse proxy (such as Nginx) layer, so that each resource type can be finely controlled.


CSRF protection

Cross-site request forgery (CSRF) uses the user's logged-in identity to induce the browser to issue malicious requests without the user's knowledge. Django's native CSRF middleware can prevent most situations.

1. Don’t touch the middleware

make suredjango.middleware.csrf.CsrfViewMiddlewareexistMIDDLEWARE, and in the correct order. It is enabled by default, so do not remove it.

2. Put the CSRF token in the form

All forms submitted through POST requests must include{% csrf_token %}

<form method="post">
    {% csrf_token %}
    <input type="text" name="comment">
    <button type="submit">提交</button>
</form>

3. Don’t forget the token when making AJAX requests

When using Axios on the front end, you can automatically bring the CSRF token. First get it from the cookie, and then set it in the request header:

// 从Cookie获取csrftoken
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

// 配置Axios
import axios from 'axios';
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';

4. Key settings reinforcement

The production environment must be configured tightly:

# settings.py
CSRF_COOKIE_SECURE = True              # 仅通过HTTPS传输cookie
CSRF_COOKIE_SAMESITE = 'Strict'        # 严格限制跨站请求携带cookie
CSRF_TRUSTED_ORIGINS = ['https://yourdomain.com']  # 信任的来源

SQL injection protection

SQL injection controls the database by splicing malicious SQL statements, which may lead to data leakage or even server control. Django ORM is the cornerstone of preventing SQL injection.

1. Never splice SQL

The following operation is to create vulnerabilities yourself: :::danger Never do this

# 危险!SQL拼接
username = request.GET.get('username')
user = User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")

:::

2. Correct use of ORM and parameterization

All queries should be done via an ORM or parameterized native SQL: ::: tip safe usage

# 安全:ORM 自动参数化
username = request.GET.get('username')
user = User.objects.filter(username=username).first()

# 原生 SQL 也必须参数化
user = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])

# cursor同样需要参数化
from django.db import connection
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM auth_user WHERE username = %s", [username])
    user = cursor.fetchone()

:::

These writing methods can ensure that user input is treated as data instead of being interpreted as SQL code, preventing injection from the source.


Other core security protection

1. Clickjacking Protection

A clickjacking attack hides your page in a transparent iframe to induce users to click. django has built-inXFrameOptionsMiddleware, the appropriate response header is set by default.

The policy can be adjusted through configuration:

# settings.py(可选调整)
X_FRAME_OPTIONS = 'DENY'  # 完全禁止(生产环境推荐)
# X_FRAME_OPTIONS = 'SAMEORIGIN'  # 仅允许同源页面内嵌

2. File upload security

File upload is a high-risk operation. To make the upload safe, it must be verified from three dimensions:

  1. Extension whitelist
  2. MIME type checking
  3. File content integrity verification (especially pictures)

The following is an example implemented using django forms, usingpython-magicandPillow

# myapp/forms.py
from django import forms
from PIL import Image
import magic

class SafeFileUploadForm(forms.Form):
    file = forms.FileField()
    ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}
    ALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/gif'}

    def clean_file(self):
        file = self.cleaned_data['file']
        
        # 1. 验证扩展名
        ext = '.' + file.name.split('.')[-1].lower()
        if ext not in self.ALLOWED_EXTENSIONS:
            raise forms.ValidationError('不允许的文件类型')
        
        # 2. 验证MIME类型(用python-magic)
        file.seek(0)
        mime = magic.Magic(mime=True)
        mime_type = mime.from_buffer(file.read(1024))
        file.seek(0)
        if mime_type not in self.ALLOWED_MIME_TYPES:
            raise forms.ValidationError('文件类型不匹配')
        
        # 3. 验证图片完整性
        if mime_type.startswith('image/'):
            try:
                img = Image.open(file)
                img.verify()
                file.seek(0)
            except Exception:
                raise forms.ValidationError('图片文件损坏或不安全')
        
        return file

3. Password security

Password storage and verification can never be sloppy. Django provides a complete password validator, which must be enabled and enhanced in the production environment:

# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 12}},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

Production environment security configuration

1. Sensitive information is managed using environment variables

Sensitive data (keys, database passwords) should never be hardcoded in the code. Recommendedpython-decouplefrom.envFile reading:

Install first:

pip install python-decouple

create.envfile (remember to add to.gitignore):

SECRET_KEY=your-production-secret-key-never-expose
DEBUG=False
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
DATABASE_URL=postgres://user:pass@localhost/db

existsettings.pyLoad in:

from decouple import config, Csv

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

2. One-click deployment check

Django has a built-in check command that can detect many common deployment security issues:

python manage.py check --deploy

After running, follow the prompts to correct any problems, especiallySECRET_KEYNot set orDEBUGStill enable such high-risk items.


Security Testing and Tools

1. Write basic security test cases

Write security requirements as automated tests so that every change can be automatically verified:

# myapp/tests/test_security.py
from django.test import TestCase, Client
from django.contrib.auth.models import User
from django.urls import reverse

class SecurityTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user('test', 'test@test.com', 'StrongPass123!')
    
    def test_csrf_protection(self):
        # 无CSRF令牌的POST应该被拒绝
        response = self.client.post(reverse('submit_comment'), {'text': 'test'})
        self.assertEqual(response.status_code, 403)
    
    def test_xss_protection(self):
        # 输入脚本应该被转义,原本的脚本字符串不会出现在响应页面里
        response = self.client.get(f"{reverse('search')}?q=<script>alert(1)</script>")
        self.assertNotContains(response, '<script>alert(1)</script>')
        self.assertContains(response, '&lt;script&gt;alert(1)&lt;/script&gt;')

2. Commonly used security scanning tools

  • Bandit: Static analysis of security issues in Python code

    pip install bandit
    bandit -r .
  • Safety: Check for known vulnerabilities in project dependencies

    pip install safety
    safety check

Running these tools regularly can help you plug known vulnerabilities in a timely manner.


Summary of this chapter

  1. Django has blocked most web attacks for you, but you must configure and use it correctly
  2. There are only two core concepts: use a whitelist for input and escape all output
  3. Never splice SQL, stick to ORM or parameterized queries
  4. The production environment must be shut downDEBUG, force the use of HTTPS, and enable various security headers
  5. Make good use of automation tools:check --deploy, Bandit, Safety, regular scanning

Security is not a one-time configuration but an ongoing engineering habit.

Next step to learn


🔗 Recommended related tutorials

🏷️ Core Tags:django安全 XSS防护 CSRF防护 SQL注入 生产环境配置