امنیت در Django: بهترین روشها
چرا پروژههای Django در برابر حملات رایج آسیبپذیر میشوند و چطور میتوان از ابتدا امنیت را در معماری پروژه ساخت؟ راهنمای عملی امنیت Django از تنظیمات پایه تا CSRF و SQL Injection.
اولین باری که در یک پروژه Django با گزارش نفوذ روبهرو شدم، مقصر یک باگ پیچیده نبود؛ کسی DEBUG = True را روی محیط production فراموش کرده بود و صفحه خطا، مسیر دیتابیس و مقادیر SECRET_KEY را به مهاجم نشان داده بود. آن روز یاد گرفتم که امنیت Django (فریمورک وب پایتون محبوب) بیش از هر چیز، یک مسئله پیکربندی و انضباط است، نه یک ماژول جادویی. Django از ابتدا با ذهنیت امنیت ساخته شده و ابزارهای جدی در اختیار شما میگذارد، اما اگر آن ابزارها بهدرستی فعال نشوند، هیچ ارزشی ندارند. تجربهام میگوید بیشتر آسیبپذیریهای واقعی در پروژههای Django، از سه جا میآید: تنظیمات پیشفرض که برای production کافی نیستند، بیتوجهی به کلاسهای دفاعی که Django ارائه میکند، و نادیدهگرفتن مرز بین کد خودتان و کد کتابخانهها. در ادامه همان مسیری را میگویم که در پروژههای واقعی برای امنسازی Django طی میکنم.
فلسفه امنیتی Django در یک نگاه
Django از ابتدا با این فرض ساخته شده که بخش بزرگی از آسیبپذیریهای وب، از اشتباهات قابلپیشگیری میآید. به همین دلیل، برخی از کلاسهای دفاعی مثل محافظت در برابر CSRF (Cross-Site Request Forgery) و XSS (Cross-Site Scripting) بهصورت پیشفرض فعالاند. این پیشفرضها اما، سه شرط دارند: اول، شما نباید آنها را خاموش کنید. دوم، باید بدانید کجا از کنترل خودکار خارج میشوید (مثلاً وقتی mark_safe یا |safe بهکار میبرید). سوم، باید لایههای امنیتی سطح سرور و سطح پیکربندی را روی همان پایه بگذارید. اگر با مفهوم کلی Django آشنا نیستید، مقایسههای مرتبط در بهترین زبانهای بکاند پیشزمینه خوبی میدهد. همچنین اگر مسئولیتهای کلی بکاند را نمیشناسید، بکاند چیست و چه وظایفی دارد نقطه شروع مناسبی است.
امنیت Django، بیشتر از جنس «فعال کردن» است تا «نوشتن»؛ اگر بدانید کدام کلید را کجا بزنید، ۸۰ درصد دفاع شما آماده است.
گام اول: تنظیمات امن settings.py
فایل settings.py قلب پیکربندی امنیتی Django است. یک سری تنظیمات که باید برای محیط production اعمال شوند:
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
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
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
CSRF_TRUSTED_ORIGINS = ['https://example.com', 'https://www.example.com']
اشتباه رایج در تجربه من: جدا کردن settings برای محیطهای development و production با یک فایل جداگانه، و بعد فراموش کردن اینکه در production، DEBUG = False باشد. من معمولاً از متغیرهای محیطی برای این کار استفاده میکنم:
import os
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
اگر روی امنیت پروژهتان حساس هستید، این تنظیمات پایه را جدی بگیرید؛ هر یک از آنها یک کلاس دفاعی مستقل را فعال میکند. تحلیل خطاهای ناشی از پیکربندی نادرست در رفع مشکلات SMTP هم قابل قیاس است؛ همان منطق «ترتیب پیکربندی قبل از عملکرد».
مدیریت SECRET_KEY و مقادیر حساس
SECRET_KEY یک رشته امضاشده است که Django برای امضای کوکیها، توکنهای CSRF و مقادیر نشست استفاده میکند. سه قاعده حیاتی:
- هرگز آن را در مخزن Git قرار ندهید. از متغیر محیطی یا سرویس مدیریت secret استفاده کنید.
- برای هر محیط، یک کلید جداگانه بسازید. کلید لوکال و production باید متفاوت باشند.
- اگر احتمال لو رفتن وجود دارد، همان روز آن را بچرخانید. بعد از چرخش، همه نشستها و توکنهای CSRF باطل میشوند، که برای پاکسازی امنیتی، همان چیزی است که میخواهید.
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
برای بانک اطلاعاتی، رمز عبور و کلیدهای API هم همین منطق را دنبال کنید: هیچ مقدار حساسی نباید در کد باشد. در پروژههای سازمانی، از ابزارهایی مثل django-environ یا python-decouple استفاده میکنم. اگر روی زیرساخت خود میزبانی دارید، راهنمای hardening سرور در چگونه سرور را مدیریت و نگهداری کنیم مکمل این بحث است.
CSRF و کلاسهای دفاعی بومی
CSRF (Cross-Site Request Forgery) یک حمله است که در آن، سایت مهاجم از اعتماد مرورگر کاربر به سایت شما سوءاستفاده میکند. Django با فعال بودن django.middleware.csrf.CsrfViewMiddleware، بهطور پیشفرض شما را در برابر این حمله محافظت میکند. اما چهار نکته اجرایی:
- در همه فرمهای POST، تگ
{% csrf_token %}را قرار دهید. - در قالبهای AJAX، توکن را از کوکی بخوانید و در هدر
X-CSRFTokenقرار دهید. - از خاموش کردن middleware برای راحتی صرفنظر کنید؛ اگر یک view خاص به آن نیاز ندارد، از
@csrf_exemptروی همان view استفاده کنید، نه در سطح middleware. - کوکی CSRF را با تنظیم
CSRF_COOKIE_SECURE = Trueروی HTTPS محدود کنید.
مفاهیم مشابه در Django و در فریمورکهای دیگر یکی است؛ اگر میخواهید نسخه وردپرسی این بحث را ببینید، CSRF چیست و چگونه از آن جلوگیری کنیم راهنمای معادل است.
XSS و پاکسازی خروجی
XSS (Cross-Site Scripting) هنوز یکی از رایجترین آسیبپذیریهای وب است. Django در قالبها، بهطور پیشفرض همه متغیرها را escape میکند. این تصمیم، جلوی بخش بزرگی از حملات XSS را میگیرد. نقطه خطر، همان جایی است که شما بهطور آگاهانه آن escape را خاموش میکنید:
<!-- خطرناک -->
{{ user_input|safe }}
<!-- خطرناک در کد -->
from django.utils.safestring import mark_safe
return mark_safe(user_input)
قاعده من: خروجی HTML که از کاربر یا از دیتابیس میآید، هرگز نباید با safe یا mark_safe صادر شود، مگر اینکه پیش از آن، با کتابخانهای مثل bleach پاکسازی شده باشد. اگر کاربر نیاز به ورودی HTML دارد (مثلاً ویرایشگر WYSIWYG)، از یک پاکساز قوی استفاده کنید که فقط تگهای مجاز را نگه دارد. نشانههای آلودگی و راهکارهای پاکسازی در حملات XSS و راههای مقابله آمده است.
SQL Injection و ORM
Django ORM (Object-Relational Mapping) بهطور پیشفرض در برابر SQL Injection مقاوم است، چون پارامترها را بهصورت امن به دیتابیس میفرستد. نقطه خطر، جایی است که شما از ORM خارج میشوید و دست به کوئری خام میزنید:
# خطرناک — هرگز این کار را نکنید
from django.db import connection
cursor = connection.cursor()
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# امن — استفاده از پارامتر
cursor.execute("SELECT * FROM users WHERE username = %s", [username])
در تجربه من، بیشتر آسیبپذیریهای SQL در پروژههای Django، از سه جا میآیند: کد خام SQL در یک view، استفاده از extra() بدون پارامتر، و استفاده از کتابخانههای شخص ثالث که خودشان کوئری خام میزنند. آخرین مورد خطرناکترین است، چون در کنترل شما نیست. تحلیل کامل در SQL Injection چیست و چگونه جلوگیری کنیم آمده است.
ORM Django، شما را از ۹۰ درصد SQL Injection نجات میدهد؛ اما ۱۰ درصد باقیمانده، همان جایی است که توسعهدهنده تصمیم میگیرد «خودش بنویسد».
احراز هویت، رمز عبور و نشستها
سیستم احراز هویت Django (django.contrib.auth) یکی از پختهترین بخشهای فریمورک است. سه نکته اجرایی:
- از هش رمز عبور پیشفرض استفاده کنید. Django بهطور پیشفرض از PBKDF2 استفاده میکند؛ برای پروژههای حساس، Argon2 یا BCrypt را با نصب کتابخانه مناسب فعال کنید.
- احراز هویت دو مرحلهای (2FA) را جدی بگیرید. برای پنل مدیریت و حسابهای با دسترسی بالا، حتماً 2FA اضافه کنید. مفهوم کامل در فعالسازی 2FA آمده، منطق آن مستقل از پلتفرم است.
- نشستها را با HTTPS محدود کنید. تنظیمات
SESSION_COOKIE_SECURE،SESSION_COOKIE_HTTPONLYوSESSION_EXPIRE_AT_BROWSER_CLOSEدر production ضروریاند.
در پروژههای واقعی، محدودسازی نرخ لاگین (rate limiting) را با ابزارهایی مثل django-ratelimit یا در لایه وبسرور اعمال میکنم؛ چون Django خودش ابزار آمادهای برای این کار ندارد و بدون آن، حمله brute force میتواند خیلی سریع رمزهای ضعیف را کشف کند. راهنمای معادل وردپرسی در حمله brute force چیست آمده است.
هدرهای امنیتی HTTP
هدرهای امنیتی، لایهای بین مرورگر و سرور هستند که رفتار مرورگر را در برابر حملات رایج محدود میکنند. Django بخش بزرگی از این هدرها را با تنظیمات آماده ارائه میدهد:
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
هدر Content-Security-Policy (CSP) که در Django بهطور پیشفرض وجود ندارد، را باید با ابزاری مثل django-csp اضافه کنید. CSP در تجربه من تفاوت محسوسی در کاهش موفقیت حملات XSS میسازد؛ چون حتی اگر یک اسکریپت مخرب به صفحه تزریق شود، مرورگر آن را اجرا نمیکند. تنظیم درست CSP نیاز به دقت دارد (پیشنهاد میکنم با حالت report-only شروع کنید)، اما برای پروژههای جدی، ارزشش را دارد. مفاهیم معادل در وردپرس در هدرهای امنیتی HTTP آمده است.
آپلود فایل و مدیریت رسانه
آپلود فایل، یکی از پرخطرترین بخشهای هر پروژه وب است. سه قاعده در Django:
- نوع فایل را با محتوا بررسی کنید، نه فقط با پسوند. مهاجم میتواند فایل PHP را با پسوند .jpg بفرستد.
- فایلهای آپلودی را در مسیری خارج از ریشه اجرایی سرور قرار دهید. اگر سرور آپاچی یا Nginx دارید، پوشه uploads نباید هیچوقت کدی را اجرا کند.
- نام فایل را بازنویسی کنید. نام فایل کاربر، میتواند شامل کاراکترهای خطرناک باشد. یک UUID یا هش تولید کنید و همان را نام فایل قرار دهید.
import uuid
from django.core.files.storage import FileSystemStorage
def save_upload(f):
fs = FileSystemStorage(location='/safe/path/')
name = f"{uuid.uuid4()}{f.name}"
return fs.save(name, f)
برای فایلهای حساس، حتی سرو کردن آنها را از طریق Django view با احراز هویت انجام دهید، نه مستقیم از استاتیک سرور.
کتابخانههای شخص ثالث و وابستگیها
در تجربهام، بیشتر آسیبپذیریهای جدی که در پروژههای Django دیدهام، نه در کد خود پروژه، که در کتابخانههای شخص ثالث بودند. سه اقدام ضروری:
- پایش وابستگیها: از
pip-auditیاsafetyاستفاده کنید تا در CI/CD، آسیبپذیریهای شناختهشده لو بروند. - قفل کردن نسخهها: فایل
requirements.txtباید نسخههای دقیق داشته باشد، تا آپدیت ناخواسته رخ ندهد. - آپدیت دورهای: هر ماه، وابستگیها را در محیط staging آپدیت کنید و تستهای امنیتی را اجرا کنید. این عادت در کاهش آسیبپذیریهای Zero Day مهم است؛ مفهوم Zero Day در آسیبپذیری Zero Day چیست آمده است.
نکتهای که در پروژههای واقعی مهم است: هر کتابخانه شخص ثالث، یک در جدید به سرور شماست. قبل از اضافه کردن هر بسته، به سه سؤال پاسخ دهید: آخرین انتشارش کِی بوده، چند همکار فعال دارد، و آیا در فهرست CVE آسیبپذیریهای بحرانی ثبتشده دارد یا نه.
مانیتورینگ و پاسخ به حادثه
امنیت بدون پایش، فقط یک فرض است. سه سطح مانیتورینگ که در پروژهها فعال میکنم:
- لاگگیری سطح اپلیکیشن: Django logging را در سطح WARNING و بالاتر فعال کنید و لاگها را بیرون از سرور نگه دارید.
- هشدار روی رفتار غیرعادی: مثلاً تعداد بالای ورود ناموفق از یک IP، یا خطاهای ۵xx در تعداد بالا.
- اسکن دورهای: ابزارهای اسکن آسیبپذیری را ماهانه اجرا کنید؛ فهرست ابزارها در اسکنرهای آسیبپذیری وب آمده است.
در صورت وقوع حادثه، پروتکل سهگامی زیر را اجرا میکنم: اول، بکاپ فوری از «لحظه جرم» برای تحلیل بعدی. دوم، قطع کانال ورود (پسورد یا کلید جاری را باطل کنید). سوم، بازنگری همه وابستگیها و کد خودنوشت با فرض اینکه یک مسیر ورود وجود داشته که ما ندیدهایم. مفاهیم تکمیلی در پاسخ به حادثه وردپرسی در راهنمای پاکسازی سایت هکشده آمده، اما منطق مستقل از پلتفرم است.
جدول مرجع
| کلاس حمله | ابزار بومی Django | تنظیم یا نکته کلیدی |
|---|---|---|
| CSRF | CsrfViewMiddleware | {% csrf_token %} در همه فرمها |
| XSS | Escape پیشفرض قالب | خودداری از mark_safe و safe |
| SQL Injection | ORM با پارامتر | پرهیز از کوئری خام |
| Clickjacking | X_FRAME_OPTIONS | مقدار DENY یا SAMEORIGIN |
| Sniffing | SECURE_CONTENT_TYPE_NOSNIFF | فعال در production |
| نشستهای ناامن | SESSION_COOKIE_SECURE | فقط روی HTTPS |
| رمز عبور ضعیف | Argon2 (بهینه) | فعال با PASSWORD_HASHERS |
| Brute Force | django-ratelimit | سقف تلاش ورود در بازه زمانی |
| Zero Day وابستگی | pip-audit، safety | پایش ماهانه در CI/CD |
پرسشهای کوتاه
آیا Django بهطور پیشفرض امن است؟ بله، بهطور پیشفرض ابزارهای جدی در اختیار شما میگذارد، اما پیشفرضها برای development طراحی شدهاند. برای production باید تنظیمات را دقیقتر کنید. تفاوت این دو، همان تفاوت بین «پایه امن» و «امنیت عملیاتی» است.
آیا استفاده از ORM کافی است تا SQL Injection نداشته باشم؟ در ۹۰ درصد موارد بله؛ اما اگر جای کوئری خام استفاده کنید یا از کتابخانهای که کوئری خام میزند بهره ببرید، این حفاظت شکسته میشود. قاعده من: کوئری خام را فقط با پارامترهای امن و بعد از بررسی دوطرفه بنویسید.
پنل ادمین Django چقدر امن است؟ خودِ پنل امن است، اما مسیر /admin/ یک هدف شناختهشده برای اسکنرهای خودکار است. پیشنهاد من: مسیر ادمین را تغییر دهید، 2FA را روی همه حسابهای مدیریتی فعال کنید، و IPهای مجاز را در صورت امکان محدود کنید.
آیا باید از django-allauth یا سیستم احراز هویت بومی استفاده کنم؟ اگر نیاز به ورود با شبکههای اجتماعی یا مدیریت پیشرفته کاربران دارید، django-allauth انتخاب خوبی است. اگر فقط احراز هویت داخلی میخواهید، سیستم بومی Django کافی و امن است.
چقدر طول میکشد امنیت یک پروژه Django را بازبینی کنم؟ در تجربه من، برای یک پروژه متوسط، حدود یک تا دو روز کاری برای بازبینی کامل تنظیمات، کد و وابستگیها. زمان بیشتر، در رفع مشکلات کشفشده صرف میشود، نه در بازبینی.
از نگاه معماری امنیتی
برای توسعهدهندگان و معماران Django که با پروژههای بزرگ و حساس کار میکنند، امنیت Django یک لایه در کنار لایههای دیگر است، نه جایگزین آنها. سه اصل معماری که در تجربهام بیشترین اثر را داشتهاند. اول، مرزبندی روشن بین لایههای مسئولیت: فریمورک وظیفه دفاعهای عمومی (CSRF، XSS، SQL Injection) را دارد، وبسرور وظیفه TLS و هدرهای سطح شبکه، و زیرساخت وظیفه فایروال و جداسازی. هر لایه را مستقل بسنجید و انتظار دفاع از لایهای را نداشته باشید که در آن تخصصی ندارد. دوم، جداسازی محیطها: development، staging و production باید از نظر تنظیمات امنیتی، بهطور کامل از هم جدا باشند. کوچکترین سهلانگاری در این جداسازی، میتواند کل زحمتهای امنیتی را بیاثر کند. سوم، «امنیت بهعنوان کد»: بهجای اعمال تنظیمات امنیتی بهصورت دستی، آنها را در CI/CD و در قالب پروژه (project template) تبدیل به کد کنید؛ این کار اجازه میدهد هر پروژه جدید از روز اول با همان استاندارد شروع شود و بررسیهای امنیتی، بخشی از خط تولید نرمافزار شوند، نه یک مرحله جدا که همیشه به تأخیر میافتد. در تجربه من، تیمهایی که این سه اصل را رعایت میکنند، در بازههای زمانی کوتاهتر و با هزینه کمتر، پروژههایشان را امنتر میکنند؛ چرا که امنیت برایشان یک کار جانبی نیست، بخشی از معماری روزمره است.
جمع این مسیر
امنیت در Django، ترکیبی از تنظیمات درست، استفاده آگاهانه از ابزارهای بومی، و انضباط در مدیریت وابستگیها و مقادیر حساس است. تجربهام میگوید سه اقدام اول بیشترین بازده را دارند: تنظیمات production را کامل کنید، مقادیر حساس را از کد بیرون بکشید، و پایش وابستگیها را در CI/CD راه بیندازید. اگر امروز فقط یک کار بکنید، فایل settings.py خود را باز کنید و هر یک از تنظیمات امنیتی این مقاله را بر اساس نیاز پروژه اعمال کنید؛ همان یک قدم، بخش بزرگی از دفاع را میسازد. اگر تجربهای از امنسازی Django دارید که در منابع فارسی کمتر گفته شده (مثلاً تنظیم خاص CSP یا پیکربندی خاص 2FA)، در دیدگاهها بنویسید؛ همان تجربهها، برای نفر بعدی از هر راهنمای عمومی ارزشمندتر است. 🛡️