۲۰ اشتباه رایج امنیتی در Django که میتوانند امنیت وبسایت شما را به خطر بیندازند
اگر توسعهدهنده پایتون باشید، احتمالاً Django اولین فریمورکی است که هنگام ساخت یک وبسایت یا API به ذهنتان میرسد. این فریمورک سالهاست که به عنوان یکی از امنترین فریمورکهای توسعه وب شناخته میشود و بسیاری از شرکتهای بزرگ، استارتاپها و سازمانهایی که با اطلاعات حساس کاربران سروکار دارند، از آن استفاده میکنند.
دلیل این محبوبیت هم کاملاً مشخص است. Django به صورت پیشفرض از مکانیزمهای امنیتی قدرتمندی مانند موارد زیر بهره میبرد:
محافظت در برابر SQL Injection
جلوگیری از حملات Cross-Site Scripting (XSS)
محافظت در برابر Cross-Site Request Forgery (CSRF)
جلوگیری از Clickjacking
الگوریتمهای امن برای هش کردن رمزهای عبور
سیستم مدیریت Session ایمن
Middlewareهای امنیتی داخلی
با وجود تمام این امکانات، یک تصور اشتباه میان بسیاری از برنامهنویسان وجود دارد:
استفاده از Django به معنای امن بودن کامل برنامه نیست.
واقعیت این است که بخش زیادی از آسیبپذیریهای موجود در پروژههای Django، به خود فریمورک مربوط نمیشوند؛ بلکه ناشی از پیکربندی نادرست، رعایت نکردن اصول امنیتی و اشتباهات برنامهنویس هستند.
در بررسی پروژههای مختلف Django بارها با مواردی مانند این مواجه شدهام:
فعال بودن
DEBUG=Trueروی سرور اصلیقرار دادن
SECRET_KEYداخل مخزن Gitغیرفعال کردن CSRF برای رفع یک خطای موقت
اعتماد بیش از حد به دادههای ارسالی کاربران
ذخیره اطلاعات حساس داخل سورس پروژه
در تمامی این موارد، مشکل از Django نبوده؛ بلکه از نحوه استفاده نادرست از قابلیتهای امنیتی آن بوده است.
فرقی نمیکند در حال توسعه یک وبسایت شخصی باشید، یک فروشگاه اینترنتی، یک سرویس SaaS یا یک REST API با هزاران کاربر؛ شناخت این اشتباهات میتواند سطح امنیت پروژه شما را به شکل چشمگیری افزایش دهد.
در این مقاله، ۲۰ مورد از رایجترین اشتباهات امنیتی در Django را بررسی میکنیم، دلیل خطرناک بودن هرکدام را توضیح میدهیم و بهترین روش رفع آنها را بر اساس استانداردهای روز Django معرفی خواهیم کرد.
در پایان نیز یک چکلیست کاربردی در اختیار خواهید داشت که میتوانید قبل از انتشار هر پروژه Django از آن استفاده کنید.
چرا Django امن است اما به صورت پیشفرض کاملاً امن نیست؟
یکی از بزرگترین نقاط قوت Django این است که امنیت از همان ابتدا در معماری این فریمورک در نظر گرفته شده است. برخلاف بسیاری از فریمورکها که برای قابلیتهای امنیتی به افزونههای جانبی وابسته هستند، Django بسیاری از مکانیزمهای امنیتی را به صورت داخلی ارائه میدهد.
برخی از مهمترین قابلیتهای امنیتی Django عبارتاند از:
جلوگیری از SQL Injection از طریق ORM
محافظت داخلی در برابر CSRF
Escape خودکار کدهای HTML در Templateها
الگوریتمهای قدرتمند هش رمز عبور
محافظت در برابر Clickjacking
Security Middleware
اعتبارسنجی رمز عبور
امکانات امنیتی Session
اگر این قابلیتها به درستی استفاده شوند، بخش بزرگی از آسیبپذیریهای رایج وب به صورت خودکار برطرف خواهند شد.
اما یک نکته بسیار مهم وجود دارد.
تمام این قابلیتها بر یک فرض اساسی بنا شدهاند:
برنامهنویس از Django مطابق استانداردهای توصیهشده استفاده کند.
برای مثال:
اگر به جای ORM از Raw SQL استفاده کنید، محافظت در برابر SQL Injection از بین میرود.
اگر
mark_safe()را روی دادههای کاربران اعمال کنید، عملاً محافظت در برابر XSS را غیرفعال کردهاید.اگر تمام Viewها را با
@csrf_exemptعلامتگذاری کنید، CSRF دیگر معنایی نخواهد داشت.اگر سایت از HTTPS استفاده نکند، کوکیهای امن نیز تأثیر چندانی نخواهند داشت.
به بیان سادهتر، Django ابزارهای امنیتی را در اختیار شما قرار میدهد؛ اما استفاده صحیح از آنها کاملاً بر عهده توسعهدهنده است.
۱. فعال بودن DEBUG=True در محیط Production
اگر بخواهیم فقط یک اشتباه امنیتی را به عنوان مهمترین اشتباه توسعهدهندگان Django معرفی کنیم، بدون شک آن اشتباه فعال بودن حالت Debug در محیط Production است.
همین تنظیم ساده تاکنون باعث افشای اطلاعات حساس هزاران وبسایت شده است.
چرا این موضوع خطرناک است؟
زمانی که DEBUG=True باشد، در صورت بروز هرگونه خطا، Django به جای نمایش یک صفحه خطای ساده، اطلاعات بسیار دقیقی از ساختار داخلی پروژه نمایش میدهد.
از جمله:
مسیر فایلهای پروژه
نسخه Python
نسخه Django
تنظیمات پروژه
Middlewareها
لیست اپلیکیشنها
مسیر URLها
اطلاعات پایگاه داده
Packageهای نصبشده
Stack Trace کامل
متغیرهای محلی
اگرچه این اطلاعات هنگام توسعه بسیار مفید هستند، اما برای یک مهاجم نیز ارزش فوقالعادهای دارند.
به عنوان مثال، تنها با ایجاد یک خطای 500 ممکن است مهاجم بتواند اطلاعات زیر را به دست آورد:
ساختار پروژه
نوع دیتابیس
کتابخانههای استفادهشده
برخی تنظیمات حساس
Endpointهای داخلی
این اطلاعات فرآیند شناسایی (Reconnaissance) را برای مهاجم بسیار سادهتر میکنند.
پیکربندی اشتباه
DEBUG = Trueاگر این مقدار روی سرور اصلی باقی بماند، هر خطا میتواند منجر به افشای اطلاعات حساس شود.
روش صحیح
در محیط Production همیشه مقدار Debug را غیرفعال کنید.
DEBUG = Falseبه جای نمایش جزئیات خطا به کاربران، از سیستم Logging برای ثبت خطاها استفاده کنید تا فقط توسعهدهندگان به اطلاعات فنی دسترسی داشته باشند.
همچنین بهتر است مقدار Debug از طریق متغیرهای محیطی کنترل شود:
import os
DEBUG = os.getenv("DEBUG", "False") == "True"سپس روی سرور اصلی:
DEBUG=Falseبهترین روشها (Best Practices)
هرگز پروژه را با
DEBUG=Trueمنتشر نکنید.تنظیمات محیط توسعه و Production را از هم جدا کنید.
از Environment Variable برای کنترل Debug استفاده کنید.
سیستم Logging مناسب راهاندازی کنید.
برای خطاهای 500 یک صفحه سفارشی نمایش دهید.
نکته: قبل از هر Deployment، یکی از اولین مواردی که باید بررسی کنید، مقدار
DEBUG=Falseاست. این سادهترین بررسی امنیتی محسوب میشود، اما یکی از رایجترین اشتباهات نیز هست.
۲. قرار دادن SECRET_KEY داخل سورس پروژه یا مخزن Git
اگر بخواهیم مهمترین فایل محرمانه یک پروژه Django را نام ببریم، بدون شک SECRET_KEY یکی از اولین موارد خواهد بود.
این کلید در بخشهای مختلفی از Django استفاده میشود، از جمله:
امضای دیجیتال Sessionها
توکنهای CSRF
لینکهای بازیابی رمز عبور
Signed Cookieها
رمزنگاری برخی دادههای امنیتی
اگر این کلید در اختیار افراد غیرمجاز قرار بگیرد، بخشی از مکانیزمهای امنیتی برنامه شما عملاً بیاعتبار میشوند.
چرا این موضوع خطرناک است؟
بسیاری از برنامهنویسان پروژه را با تنظیمات پیشفرض Django ایجاد میکنند و همان SECRET_KEY تولیدشده را داخل فایل settings.py نگه میدارند.
مشکل زمانی ایجاد میشود که پروژه در GitHub، GitLab یا هر سیستم کنترل نسخه دیگری قرار میگیرد.
حتی اگر مخزن خصوصی باشد، همچنان احتمال موارد زیر وجود دارد:
عمومی شدن اتفاقی Repository
نشت اطلاعات از طریق Backup
اشتراکگذاری ناخواسته سورس پروژه
دسترسی یکی از اعضای تیم به اطلاعات حساس
به همین دلیل، اگر حتی یک بار SECRET_KEY در Git ثبت شود، بهتر است آن را افشا شده فرض کنید.
روش اشتباه
# settings.py
SECRET_KEY = "django-insecure-random-secret-key"در این حالت کلید امنیتی برای همیشه در تاریخچه Git ذخیره خواهد شد.
حتی اگر بعداً آن را حذف کنید، همچنان در Commitهای قبلی وجود خواهد داشت.
روش صحیح
بهترین راهکار، استفاده از Environment Variables است.
import os
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]اگر هنگام توسعه از python-dotenv استفاده میکنید:
from dotenv import load_dotenv import os
load_dotenv()
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY")سپس داخل فایل .env مقدار کلید را قرار دهید:
DJANGO_SECRET_KEY=your-super-long-random-secret-keyو مهمتر از همه، فایل .env را هرگز داخل Git قرار ندهید.
بهترین روشها
از یک کلید کاملاً تصادفی و طولانی استفاده کنید.
SECRET_KEY را داخل Environment Variable نگهداری کنید.
هرگز آن را داخل
settings.pyقرار ندهید.فایل
.envرا داخل.gitignoreقرار دهید.برای محیط Development، Staging و Production از کلیدهای متفاوت استفاده کنید.
اگر کلید لو رفت، فوراً آن را تغییر دهید.
نکته امنیتی: اگر حتی یک بار SECRET_KEY را داخل یک مخزن عمومی قرار دادهاید، فرض کنید این کلید افشا شده است و بدون معطلی آن را جایگزین کنید.
۳. پیکربندی اشتباه ALLOWED_HOSTS
یکی دیگر از اشتباهات رایج، بیتوجهی به تنظیمات ALLOWED_HOSTS است.
برخی توسعهدهندگان برای اینکه پروژه سریعتر اجرا شود، این مقدار را خالی میگذارند یا حتی از "*" استفاده میکنند؛ در حالی که این تنظیم نقش مهمی در جلوگیری از حملات HTTP Host Header Attack دارد.
چرا این موضوع خطرناک است؟
هر درخواست HTTP دارای هدر Host است.
مثلاً:
Host: example.comاگر برنامه بدون بررسی این مقدار به آن اعتماد کند، مهاجم میتواند هدر Host را تغییر داده و حملاتی مانند موارد زیر انجام دهد:
تولید لینکهای مخرب
آلوده کردن Cache
دستکاری لینکهای بازیابی رمز عبور
تغییر آدرسهای تولید شده توسط برنامه
Django با استفاده از ALLOWED_HOSTS این هدر را بررسی میکند و فقط درخواستهایی را میپذیرد که از دامنههای مجاز ارسال شده باشند.
پیکربندی اشتباه
ALLOWED_HOSTS = []یا حتی بدتر:
ALLOWED_HOSTS = ["*"]استفاده از "*" تقریباً اعتبارسنجی Host را غیرفعال میکند و برای محیط Production توصیه نمیشود.
روش صحیح
فقط دامنههای واقعی پروژه را تعریف کنید.
ALLOWED_HOSTS = [
"example.com",
"www.example.com",
]اگر از سرویسهای Cloud یا Load Balancer استفاده میکنید، فقط Hostهای موردنیاز را اضافه کنید.
بهترین روشها
هرگز در Production از
"*"استفاده نکنید.فقط دامنههای معتبر را وارد کنید.
بعد از اضافه شدن هر Subdomain تنظیمات را بررسی کنید.
لینکهای Password Reset را تست کنید.
قبل از انتشار پروژه تنظیمات Production را بازبینی کنید.
۴. غیرفعال کردن CSRF به جای رفع مشکل
تقریباً هر برنامهنویس Django حداقل یک بار با خطای معروف:
403 Forbidden (CSRF verification failed)مواجه شده است.
متأسفانه بعضی افراد سادهترین راه را انتخاب میکنند:
غیرفعال کردن CSRF.
این یکی از خطرناکترین اشتباهاتی است که میتوان در یک پروژه Django انجام داد.
CSRF چیست؟
فرض کنید کاربری وارد حساب بانکی خود شده است.
اگر سایت در برابر CSRF محافظت نشود، مهاجم میتواند کاربر را به صفحهای هدایت کند که بدون اطلاع او درخواست انتقال پول ارسال کند.
از آنجا که مرورگر کوکیهای ورود را به صورت خودکار ارسال میکند، ممکن است عملیات با موفقیت انجام شود.
CSRF دقیقاً برای جلوگیری از چنین حملاتی طراحی شده است.
روش اشتباه
بسیاری از توسعهدهندگان برای حذف خطا از این Decorator استفاده میکنند:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt def transfer_money(request):
...یا حتی Middleware مربوط به CSRF را حذف میکنند.
MIDDLEWARE = [
...
# "django.middleware.csrf.CsrfViewMiddleware",
]این کار شاید خطا را برطرف کند، اما یکی از مهمترین لایههای امنیتی Django را از بین میبرد.
روش صحیح
در فرمهای HTML از توکن CSRF استفاده کنید.
<form method="post">
{% csrf_token %}
</form>در درخواستهای AJAX نیز مقدار توکن را داخل هدر X-CSRFToken ارسال کنید.
اگر در حال توسعه یک REST API با JWT یا Token Authentication هستید، ابتدا بررسی کنید که آیا واقعاً به CSRF نیاز دارید یا خیر، نه اینکه بدون بررسی آن را غیرفعال کنید.
بهترین روشها
هرگز CSRF را به صورت سراسری غیرفعال نکنید.
فقط در شرایط کاملاً خاص از
@csrf_exemptاستفاده کنید.دلیل بروز خطای CSRF را پیدا کنید.
تمام فرمها و درخواستهای AJAX را با CSRF Token ارسال کنید.
قبل از انتشار پروژه، عملیات حساس را تست کنید.
نکته: اگر با خطای CSRF مواجه شدید، مشکل معمولاً از نحوه ارسال درخواست است، نه از Django.
۵. اعتماد کردن به دادههای ورودی کاربران
یکی از بزرگترین اشتباهات امنیتی این است که تصور کنیم کاربران همیشه اطلاعات صحیح وارد میکنند.
واقعیت این است که هر دادهای که از سمت کاربر دریافت میشود، غیرقابل اعتماد است؛ فرقی نمیکند آن کاربر یک انسان باشد یا یک ربات.
چرا این موضوع خطرناک است؟
فرض کنید فرم ثبتنام شما انتظار دارد:
نام کاربری بین ۳ تا ۳۰ کاراکتر باشد.
ایمیل معتبر وارد شود.
سن بین ۱۸ تا ۱۲۰ سال باشد.
اما مهاجم میتواند دادههایی مانند موارد زیر ارسال کند:
رشتههای بسیار طولانی برای مصرف منابع سرور
کدهای JavaScript
کاراکترهای غیرمجاز
مقادیر منفی
انواع داده غیرمنتظره
درخواستهایی که اصلاً از فرم شما ارسال نشدهاند
به خاطر داشته باشید:
اعتبارسنجی سمت کلاینت فقط تجربه کاربری را بهتر میکند؛ اعتبارسنجی سمت سرور امنیت را تضمین میکند.
روش اشتباه
def create_user(request):
username = request.POST["username"]
User.objects.create(username=username)در این مثال، هر مقداری که کاربر ارسال کند، مستقیماً ذخیره میشود.
روش صحیح
از Django Forms یا Serializerهای Django REST Framework استفاده کنید.
class RegistrationForm(forms.Form):
username = forms.CharField(
min_length=3,
max_length=30
)
age = forms.IntegerField(
min_value=18,
max_value=120
)در این حالت، قبل از ذخیره اطلاعات، تمامی دادهها اعتبارسنجی میشوند.
بهترین روشها
هرگز به اعتبارسنجی Frontend اعتماد نکنید.
تمام دادهها را در Backend بررسی کنید.
طول رشتهها را محدود کنید.
بازه اعداد را کنترل کنید.
ایمیل و URL را اعتبارسنجی کنید.
فیلدهای غیرمنتظره را رد کنید.
تا جای ممکن از Formها یا Serializerهای Django استفاده کنید.
اعتبارسنجی ورودی تنها برای جلوگیری از خطا نیست؛ بلکه اولین خط دفاعی در برابر بسیاری از حملات امنیتی است.
۶. استفاده ناامن از Raw SQL و ایجاد آسیبپذیری SQL Injection
یکی از مهمترین دلایلی که Django به عنوان یک فریمورک امن شناخته میشود، استفاده از ORM است. ORM به صورت خودکار کوئریها را پارامتربندی (Parameterized) میکند و مانع از حملات SQL Injection میشود.
با این حال، برخی توسعهدهندگان برای سادگی یا افزایش سرعت، ORM را کنار میگذارند و مستقیماً Queryهای SQL مینویسند. اگر این کار به شکل نادرست انجام شود، میتواند یکی از خطرناکترین آسیبپذیریهای امنیتی وب را ایجاد کند.
چرا SQL Injection خطرناک است؟
در حملات SQL Injection، مهاجم تلاش میکند ورودی خود را به عنوان بخشی از دستور SQL اجرا کند.
در صورت موفقیت، ممکن است بتواند:
اطلاعات کاربران را مشاهده کند.
دادههای پایگاه داده را تغییر دهد.
رکوردها را حذف کند.
احراز هویت را دور بزند.
حتی در برخی شرایط دستورات مدیریتی روی دیتابیس اجرا کند.
به محض اینکه Queryهای SQL را به صورت دستی و با استفاده از دادههای کاربران بسازید، دیگر محافظت داخلی Django بیاثر خواهد شد.
روش اشتباه
استفاده از f-string یا الحاق رشته برای ساخت Query:
username = request.GET["username"]
query = f"""
SELECT *
FROM auth_user
WHERE username = '{username}'
"""
User.objects.raw(query)یا
cursor.execute(
f"SELECT * FROM users WHERE id={user_id}"
)اگر مقدار username یا user_id از کاربر دریافت شود، این کد آسیبپذیر خواهد بود.
روش صحیح
تا جای ممکن از ORM استفاده کنید.
User.objects.filter(username=username)در صورتی که مجبور به استفاده از Raw SQL هستید، از Queryهای پارامتربندیشده استفاده کنید.
cursor.execute(
"SELECT * FROM users WHERE id=%s",
[user_id]
)در این روش مقدار ورودی از دستور SQL جدا نگهداری میشود و امکان تزریق کد SQL وجود نخواهد داشت.
بهترین روشها
همیشه ORM را به Raw SQL ترجیح دهید.
هرگز دادههای کاربران را مستقیماً داخل Query قرار ندهید.
از f-string یا
%برای ساخت Query استفاده نکنید.در صورت نیاز به Raw SQL، فقط از Queryهای پارامتربندیشده استفاده کنید.
کدهای قدیمی پروژه را از نظر SQL Injection بازبینی کنید.
نکته: هر زمان داخل یک Query عبارت
f"یا%مشاهده کردید، بهتر است دوباره آن بخش را از نظر امنیت بررسی کنید.
۷. استفاده نادرست از mark_safe() و غیرفعال کردن Escape خودکار
یکی از قابلیتهای مهم Django، Escape خودکار محتوای HTML در Templateها است.
به عنوان مثال اگر کاربری متن زیر را ارسال کند:
<script>alert("XSS")</script>Django آن را به صورت متن ساده نمایش میدهد و اجازه اجرای JavaScript را نمیدهد.
اما برخی توسعهدهندگان برای حل مشکلات ظاهری یا نمایش HTML، از mark_safe() یا فیلتر safe استفاده میکنند و عملاً این لایه امنیتی را حذف میکنند.
چرا این موضوع خطرناک است؟
تابع mark_safe() به Django اعلام میکند که:
این محتوا کاملاً امن است؛ آن را Escape نکن.
اگر این محتوا از سمت کاربران آمده باشد، مهاجم میتواند هر نوع JavaScript دلخواهی را داخل صفحه اجرا کند.
نتیجه چنین حملهای ممکن است شامل موارد زیر باشد:
سرقت Session کاربران
اجرای عملیات به جای کاربر
سرقت اطلاعات فرمها
تغییر محتوای صفحات
هدایت کاربران به صفحات فیشینگ
روش اشتباه
from django.utils.safestring import mark_safe
comment = request.POST["comment"]
return HttpResponse(
mark_safe(comment)
)یا داخل Template:
{{ comment|safe }}اگر مقدار comment توسط کاربر وارد شده باشد، برنامه در برابر XSS آسیبپذیر خواهد شد.
روش صحیح
در اکثر مواقع اجازه دهید Django خودش محتوا را Escape کند.
{{ comment }}اگر کاربران مجاز به ارسال HTML هستند (مثلاً در یک ویرایشگر متن پیشرفته)، ابتدا HTML را با کتابخانههایی مانند Bleach یا ابزارهای مشابه پاکسازی (Sanitize) کنید و سپس نمایش دهید.
بهترین روشها
تا حد امکان از
mark_safe()استفاده نکنید.روی محتوای تولیدشده توسط کاربران فیلتر
safeاعمال نکنید.HTML کاربران را قبل از نمایش پاکسازی کنید.
Escape خودکار Django را غیرفعال نکنید.
تمام محتوای HTML کاربران را بالقوه مخرب فرض کنید.
قاعده طلایی: فقط زمانی یک محتوا را Safe علامتگذاری کنید که خودتان آن را تولید کرده باشید یا کاملاً از پاکسازی آن مطمئن باشید.
۸. بررسی احراز هویت (Authentication) اما فراموش کردن مجوز دسترسی (Authorization)
بسیاری از توسعهدهندگان فقط بررسی میکنند که کاربر وارد سیستم شده باشد.
اما سؤال مهمتر این است:
آیا این کاربر واقعاً اجازه انجام این عملیات را دارد؟
عدم بررسی مجوز دسترسی، یکی از رایجترین دلایل ایجاد آسیبپذیری Broken Access Control است که سالها در فهرست OWASP Top 10 قرار داشته است.
چرا این موضوع خطرناک است؟
فرض کنید کاربران میتوانند نوشتههای خود را ویرایش کنند.
آدرس صفحه ویرایش به شکل زیر است:
/posts/15/edit/اگر مهاجم عدد ۱۵ را به ۱۶ تغییر دهد و برنامه فقط بررسی کند که کاربر Login کرده باشد، ممکن است بتواند نوشته شخص دیگری را ویرایش کند.
این آسیبپذیری با نام IDOR (Insecure Direct Object Reference) شناخته میشود.
روش اشتباه
@login_required def edit_post(request, post_id):
post = Post.objects.get(id=post_id)
# Update postدر این حالت هر کاربر واردشده میتواند هر Post را ویرایش کند.
روش صحیح
مالکیت شیء را بررسی کنید.
@login_required def edit_post(request, post_id):
post = get_object_or_404(
Post,
id=post_id,
author=request.user
)اکنون فقط نویسنده همان مطلب قادر به ویرایش آن خواهد بود.
بهترین روشها
هرگز صرف Login بودن کاربر را کافی ندانید.
قبل از مشاهده، ویرایش یا حذف اطلاعات، مالکیت آنها را بررسی کنید.
از سیستم Permission داخلی Django استفاده کنید.
در صورت نیاز از Object-Level Permission بهره ببرید.
Endpointها را با نقشهای مختلف تست کنید.
به خاطر داشته باشید: Authentication مشخص میکند کاربر چه کسی است؛ Authorization مشخص میکند چه کاری مجاز است انجام دهد.
۹. محافظت نکردن از پنل مدیریت Django
پنل مدیریت Django یکی از قدرتمندترین قابلیتهای این فریمورک است.
اما همین ویژگی باعث شده که یکی از محبوبترین اهداف مهاجمان نیز باشد.
تقریباً تمام رباتهای اینترنتی به دنبال آدرس معروف زیر هستند:
/admin/چرا این موضوع خطرناک است؟
اگر پنل مدیریت فقط با یک رمز عبور ساده محافظت شود، مهاجم میتواند حملاتی مانند موارد زیر را انجام دهد:
Brute Force
Credential Stuffing
Password Spraying
استفاده از رمزهای افشا شده
مشکل از Django نیست؛ بلکه از نحوه مدیریت حسابهای مدیریتی است.
روش صحیح
برای افزایش امنیت پنل مدیریت:
از رمزهای عبور قوی و منحصربهفرد استفاده کنید.
احراز هویت دومرحلهای (MFA) را فعال کنید.
برای همه افراد Superuser ایجاد نکنید.
فقط دسترسیهای موردنیاز را اعطا کنید.
در صورت امکان دسترسی Admin را محدود به IPهای مشخص کنید.
لاگ ورود مدیران را بررسی کنید.
Django را همواره بهروز نگه دارید.
بهترین روشها
از اصل Least Privilege استفاده کنید.
حسابهای بدون استفاده را حذف کنید.
از حساب Superuser برای کارهای روزمره استفاده نکنید.
ورودهای ناموفق را مانیتور کنید.
MFA را برای مدیران فعال کنید.
۱۰. استفاده از سیاستهای ضعیف برای رمز عبور
حتی اگر برنامه شما کاملاً امن نوشته شده باشد، رمزهای عبور ضعیف میتوانند تمام این امنیت را از بین ببرند.
رمزهایی مانند:
12345678
password123
qwerty
letmeinهنوز هم جزو رایجترین رمزهای عبور جهان هستند.
چرا این موضوع خطرناک است؟
بیشتر حملات امروزی بر پایه حدس زدن دستی رمز عبور نیستند.
مهاجمان از میلیونها رمز عبور افشاشده در نشتهای اطلاعاتی استفاده میکنند و آنها را به صورت خودکار روی صفحات ورود امتحان میکنند.
اگر کاربران از رمزهای تکراری استفاده کرده باشند، احتمال نفوذ بسیار بالا خواهد بود.
پیکربندی اشتباه
غیرفعال کردن Password Validatorها:
AUTH_PASSWORD_VALIDATORS = []یا استفاده از حداقل محدودیت ممکن.
روش صحیح
Django به صورت پیشفرض چند Validator مناسب ارائه میدهد.
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]این Validatorها از انتخاب رمزهای:
کوتاه
رایج
کاملاً عددی
مشابه نام کاربری
جلوگیری میکنند.
بهترین روشها
Password Validatorهای Django را غیرفعال نکنید.
حداقل طول رمز عبور را افزایش دهید.
کاربران را به استفاده از Password Manager تشویق کنید.
برای حسابهای مهم MFA را فعال کنید.
فعالیتهای مشکوک ورود را مانیتور کنید.
فقط در صورت افشای رمز عبور، کاربر را مجبور به تغییر رمز کنید.
امنیت واقعی حاصل ترکیب چندین لایه دفاعی است. رمز عبور قوی، هش امن، محدودسازی تعداد تلاشهای ورود (Rate Limiting) و احراز هویت دومرحلهای در کنار هم امنیت حسابهای کاربری را تضمین میکنند.
۱۱. آپلود فایل بدون اعتبارسنجی مناسب
تقریباً تمام وبسایتها امکان آپلود فایل را در اختیار کاربران قرار میدهند؛ از تصویر پروفایل گرفته تا فایلهای PDF، ویدئو، فاکتور یا اسناد مختلف.
هرچند Django پیادهسازی قابلیت آپلود فایل را بسیار ساده کرده است، اما همین بخش یکی از رایجترین نقاط ورود مهاجمان به یک وبسایت محسوب میشود.
چرا این موضوع خطرناک است؟
اشتباه بسیاری از توسعهدهندگان این است که تصور میکنند کاربر دقیقاً همان فایلی را آپلود میکند که ادعا میکند.
برای مثال، فایلی با نام:
profile.jpgممکن است در واقع یک فایل مخرب یا حتی یک اسکریپت اجرایی باشد که تنها نام آن تغییر کرده است.
اگر تنها پسوند فایل بررسی شود، مهاجم میتواند بهراحتی محدودیتها را دور بزند.
از جمله خطرات رایج:
آپلود فایلهای مخرب
انتشار بدافزار
مصرف بیش از حد فضای ذخیرهسازی
حملات DoS با فایلهای بسیار بزرگ
جعل نوع فایل (Fake MIME Type)
روش اشتباه
بررسی فقط پسوند فایل:
if uploaded_file.name.endswith(".jpg"):
profile.image = uploaded_fileتغییر نام یک فایل مخرب به photo.jpg برای عبور از این بررسی کافی است.
روش صحیح
همیشه چند ویژگی فایل را بررسی کنید:
نوع فایل (MIME Type)
پسوند فایل
حجم فایل
محتوای واقعی فایل
برای مثال:
from django.core.exceptions import ValidationError
ALLOWED_TYPES = {
"image/jpeg",
"image/png",
}
if uploaded_file.content_type not in ALLOWED_TYPES:
raise ValidationError("Unsupported file type.")اگر فایل تصویری دریافت میکنید، بهتر است با استفاده از کتابخانه Pillow نیز صحت تصویر را بررسی کنید.
بهترین روشها
فقط پسوند فایل را بررسی نکنید.
حجم فایل را محدود کنید.
فایلهای اجرایی را مسدود کنید.
در پروژههای حساس فایلها را اسکن کنید.
فایلهای کاربران را خارج از پوشه اصلی پروژه ذخیره کنید.
نام فایل را به صورت تصادفی تولید کنید.
نکته: هیچگاه صرفاً به دلیل اینکه پسوند فایل
.jpgیا
۱۲. نمایش فایلهای کاربران از همان دامنه اصلی سایت
بسیاری از پروژههای Django فایلهای آپلود شده را به شکل زیر ارائه میکنند:
https://example.com/media/در نگاه اول مشکلی وجود ندارد؛ اما این روش میتواند در صورت وجود آسیبپذیری، اثر حملات XSS را چند برابر کند.
چرا این موضوع خطرناک است؟
فرض کنید مهاجم موفق شود یک فایل HTML مخرب آپلود کند.
اگر این فایل روی دامنه اصلی سایت قرار بگیرد:
https://example.com/media/evil.htmlمرورگر آن را به عنوان بخشی از وبسایت شما در نظر میگیرد و در برخی شرایط امکان اجرای JavaScript مخرب فراهم میشود.
روش بهتر
در محیط Production بهتر است فایلهای کاربران روی سرویسهایی مانند:
Amazon S3
Cloudflare R2
Google Cloud Storage
Azure Blob Storage
یا حداقل روی یک دامنه جداگانه مانند:
https://media.example.comذخیره شوند.
این جداسازی باعث کاهش قابل توجه ریسک حملات مبتنی بر فایل خواهد شد.
بهترین روشها
فایلها را خارج از سورس پروژه نگهداری کنید.
از دامنه جداگانه برای فایلهای کاربران استفاده کنید.
اجرای فایلهای آپلود شده را غیرفعال کنید.
Content-Type مناسب را تنظیم کنید.
۱۳. استفاده نکردن از HTTPS
امروزه استفاده از HTTPS دیگر یک قابلیت اختیاری نیست؛ بلکه یک الزام امنیتی محسوب میشود.
اگر وبسایت شما اطلاعات کاربران، حسابهای کاربری یا پرداختهای آنلاین را مدیریت میکند، باید تمام ارتباطات از طریق HTTPS انجام شوند.
چرا این موضوع خطرناک است؟
اگر سایت همچنان از HTTP استفاده کند، مهاجم حاضر در همان شبکه میتواند اطلاعاتی مانند موارد زیر را رهگیری کند:
نام کاربری و رمز عبور
Session Cookie
اطلاعات شخصی کاربران
درخواستهای API
لینکهای بازیابی رمز عبور
حتی در برخی شرایط امکان تغییر اطلاعات در مسیر انتقال نیز وجود دارد.
روش صحیح
ابتدا گواهی SSL معتبر نصب کنید.
سپس تنظیمات امنیتی Django را فعال نمایید.
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = Trueبهترین روشها
تمام درخواستهای HTTP را به HTTPS هدایت کنید.
از گواهی معتبر SSL استفاده کنید.
تمدید گواهی را خودکار کنید.
پس از هر Deployment تنظیمات HTTPS را بررسی کنید.
HTTPS فقط از اطلاعات کاربران محافظت نمیکند؛ بلکه اعتبار وبسایت شما را نیز افزایش میدهد.
۱۴. بیتوجهی به تنظیمات امنیتی Cookie
احراز هویت در Django تا حد زیادی بر پایه Cookieها انجام میشود.
اگر این Cookieها به درستی پیکربندی نشده باشند، سرقت Session کاربران بسیار سادهتر خواهد شد.
تنظیمات پیشنهادی
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = Trueاین تنظیمات باعث میشوند:
Cookieها فقط از طریق HTTPS ارسال شوند.
JavaScript نتواند به Session Cookie دسترسی داشته باشد.
احتمال سوءاستفاده در حملات XSS کاهش یابد.
بهترین روشها
همیشه Secure Cookie را فعال کنید.
از HTTPS استفاده کنید.
پس از هر بروزرسانی تنظیمات Cookie را بررسی کنید.
فرآیند Login و Logout را تست کنید.
۱۵. نادیده گرفتن Security Headerها
مرورگرهای مدرن از مجموعهای از Headerهای امنیتی پشتیبانی میکنند که فعال کردن آنها تنها چند دقیقه زمان میبرد، اما میتواند جلوی بسیاری از حملات را بگیرد.
متأسفانه بسیاری از پروژههای Django هیچکدام از این Headerها را تنظیم نمیکنند.
چرا این موضوع اهمیت دارد؟
نبود Headerهای امنیتی میتواند احتمال موفقیت حملاتی مانند موارد زیر را افزایش دهد:
Clickjacking
MIME Sniffing
برخی انواع XSS
افشای اطلاعات
تنظیمات پیشنهادی
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_REFERRER_POLICY = "same-origin"برای امنیت بیشتر نیز میتوانید از Content Security Policy (CSP) و پکیجهایی مانند django-csp استفاده کنید.
بهترین روشها
SecurityMiddleware را فعال نگه دارید.
Headerهای امنیتی را تنظیم کنید.
از CSP استفاده کنید.
Headerها را با ابزارهای امنیتی یا Developer Tools مرورگر بررسی کنید.
نکته: Security Headerها از آن دسته تنظیماتی هستند که پیادهسازی آنها زمان بسیار کمی میبرد، اما تأثیر قابل توجهی بر امنیت وبسایت دارد.
۱۶. محدود نکردن تعداد درخواستهای ورود (Rate Limiting)
صفحه ورود (Login) یکی از اصلیترین اهداف مهاجمان است. برخلاف تصور بسیاری از توسعهدهندگان، اکثر حملات به صفحات ورود از طریق آسیبپذیریهای پیچیده انجام نمیشوند؛ بلکه با استفاده از رباتهایی انجام میشوند که هزاران رمز عبور را در مدت کوتاهی امتحان میکنند.
اگر تعداد درخواستهای ورود محدود نشود، حتی قویترین رمزهای عبور نیز در برابر حملات Brute Force و Credential Stuffing در معرض خطر قرار میگیرند.
چرا این موضوع خطرناک است؟
فرض کنید صفحه ورود شما هیچ محدودیتی برای تعداد تلاشهای ناموفق ندارد.
POST /login/در این شرایط، مهاجم میتواند در هر دقیقه هزاران درخواست ارسال کند تا در نهایت به رمز عبور صحیح برسد.
روش اشتباه
یک View ورود ساده که هیچ محدودیتی روی تعداد درخواستها اعمال نمیکند:
def login_view(request):
...در چنین حالتی هیچ مکانیزمی برای جلوگیری از ارسال مداوم درخواست وجود ندارد.
روش صحیح
بهتر است تعداد درخواستهای ورود را بر اساس IP یا حساب کاربری محدود کنید.
برخی از ابزارهای مناسب برای این کار عبارتاند از:
django-axes
django-ratelimit
Cloudflare
Nginx Rate Limiting
AWS WAF
پس از چند تلاش ناموفق میتوانید اقداماتی مانند موارد زیر انجام دهید:
مسدود کردن موقت IP
افزایش تدریجی زمان انتظار
نمایش CAPTCHA
قفل کردن موقت حساب کاربری
بهترین روشها
تعداد درخواستهای ورود را محدود کنید.
درخواستهای بازیابی رمز عبور را نیز Rate Limit کنید.
ورودهای ناموفق را ثبت و بررسی کنید.
پس از چند تلاش ناموفق CAPTCHA نمایش دهید.
هرگز مشخص نکنید که مشکل از نام کاربری است یا رمز عبور.
نکته: امنیت صفحه ورود تنها به داشتن رمز عبور قوی وابسته نیست؛ بلکه محدود کردن تعداد تلاشهای ورود نیز نقش بسیار مهمی در جلوگیری از حملات دارد.
۱۷. نمایش بیش از حد اطلاعات در پیامهای خطا
پیامهای خطا باید به توسعهدهندگان کمک کنند، نه مهاجمان.
یکی از اشتباهات رایج این است که برنامه اطلاعات بسیار دقیقی درباره علت خطا در اختیار کاربر قرار میدهد.
چرا این موضوع خطرناک است؟
به این پاسخ API توجه کنید:
{
"error": "User with email admin@example.com does not exist."
}این پیام به مهاجم اعلام میکند که این ایمیل در سیستم وجود ندارد.
حالا این پیام را ببینید:
{
"error": "Incorrect password."
}در این حالت، مهاجم متوجه میشود که ایمیل معتبر است و فقط رمز عبور اشتباه وارد شده است.
با ارسال هزاران درخواست، مهاجم میتواند کاربران واقعی سیستم را شناسایی کند.
اطلاعات دیگری که ممکن است ناخواسته افشا شوند
مسیر فایلهای پروژه
ساختار پایگاه داده
Stack Trace
سرویسهای شخص ثالث
معماری داخلی برنامه
روش صحیح
به کاربران پیامهای عمومی نمایش دهید و جزئیات را فقط در Log ذخیره کنید.
به جای:
Incorrect passwordیا
Email not foundاز پیامهایی مانند این استفاده کنید:
نام کاربری یا رمز عبور وارد شده صحیح نیست.در این حالت کاربر متوجه خطا میشود، اما اطلاعات مفیدی در اختیار مهاجم قرار نمیگیرد.
بهترین روشها
Stack Trace را در محیط Production نمایش ندهید.
پیامهای احراز هویت را عمومی نگه دارید.
جزئیات خطا را فقط در Log ذخیره کنید.
اطلاعات حساس را در پاسخ API قرار ندهید.
صفحات خطای سفارشی طراحی کنید.
۱۸. بیتوجهی به Logging و مانیتورینگ امنیتی
بسیاری از توسعهدهندگان زمانی به فکر ثبت Log میافتند که حادثه امنیتی رخ داده است.
در آن زمان معمولاً دیگر خیلی دیر شده است.
چرا Logging اهمیت دارد؟
فرض کنید متوجه شدهاید یک حساب مدیریتی هک شده است.
اولین سؤالهایی که مطرح میشوند عبارتاند از:
این اتفاق چه زمانی رخ داده است؟
مهاجم از چه IP استفاده کرده است؟
چه تغییراتی در سیستم ایجاد شده است؟
چه حسابهایی تحت تأثیر قرار گرفتهاند؟
آیا حمله هنوز ادامه دارد؟
اگر هیچ Log مناسبی وجود نداشته باشد، پاسخ دادن به این سؤالها تقریباً غیرممکن خواهد بود.
چه رویدادهایی باید ثبت شوند؟
موارد مهم عبارتاند از:
ورود موفق کاربران
ورودهای ناموفق
تغییر رمز عبور
درخواست بازیابی رمز
تغییر سطح دسترسی کاربران
عملیات مدیران سیستم
آپلود فایل
فعالیتهای مشکوک API
چه اطلاعاتی نباید Log شوند؟
هرگز موارد زیر را ذخیره نکنید:
رمز عبور
Session Cookie
Tokenهای احراز هویت
اطلاعات کارت بانکی
بهترین روشها
Logها را به صورت متمرکز ذخیره کنید.
فایلهای Log را به صورت دورهای آرشیو کنید.
ورودهای ناموفق را بررسی کنید.
در صورت مشاهده فعالیت مشکوک هشدار ارسال کنید.
از دسترسی غیرمجاز به Logها جلوگیری کنید.
نکته: Logها یکی از ارزشمندترین ابزارهای امنیتی هستند، اما فقط زمانی که قبل از وقوع حادثه به درستی پیکربندی شده باشند.
۱۹. استفاده از نسخههای قدیمی Django و وابستگیها
یکی از سادهترین راههای ایجاد آسیبپذیری، بهروزرسانی نکردن نرمافزارها است.
هر سال آسیبپذیریهای جدیدی در Django و کتابخانههای جانبی آن کشف میشود و تیم توسعه Django بهطور مرتب وصلههای امنیتی منتشر میکند.
چرا این موضوع خطرناک است؟
یک پروژه Django معمولاً از کتابخانههای متعددی استفاده میکند؛ مانند:
Django
Django REST Framework
Pillow
Celery
Redis Client
psycopg
اگر حتی یکی از این کتابخانهها دارای آسیبپذیری شناختهشده باشد، اطلاعات مربوط به نحوه سوءاستفاده از آن احتمالاً به صورت عمومی منتشر شده است.
روش صحیح
بهطور منظم فایلهای زیر را بررسی کنید:
requirements.txtیا
pyproject.tomlقبل از انتشار نسخه جدید، تغییرات را در محیط Staging آزمایش کنید و اطلاعیههای امنیتی Django را دنبال کنید.
بهترین روشها
از نسخههای پشتیبانیشده Django استفاده کنید.
وصلههای امنیتی را سریع نصب کنید.
کتابخانههای بلااستفاده را حذف کنید.
وابستگیها را به صورت منظم بررسی کنید.
قبل از انتشار، نسخههای جدید را تست کنید.
امنیت یک فرآیند مداوم است، نه یک کار یکباره.
۲۰. تصور اینکه تنظیمات پیشفرض Django کافی هستند
شاید بزرگترین اشتباه امنیتی، نه یک خط کد باشد و نه یک تنظیم اشتباه.
بلکه یک طرز فکر اشتباه است.
بعضی از توسعهدهندگان تصور میکنند چون Django فریمورک امنی است، دیگر نیازی نیست درباره امنیت برنامه فکر کنند.
در حالی که هیچ فریمورکی نمیتواند از تصمیمهای اشتباه برنامهنویس جلوگیری کند.
چرا این موضوع خطرناک است؟
Django ابزارهای امنیتی بسیار قدرتمندی در اختیار شما قرار میدهد، اما اگر شما:
قابلیتهای امنیتی را غیرفعال کنید.
مجوزهای دسترسی را بررسی نکنید.
به دادههای کاربران اعتماد کنید.
اطلاعات محرمانه را افشا کنید.
پروژه را بهروزرسانی نکنید.
تنظیمات Production را اشتباه انجام دهید.
دیگر امنیت Django کمکی به شما نخواهد کرد.
قبل از انتشار هر قابلیت جدید، این سؤالها را از خود بپرسید
آیا کاربران فقط به اطلاعات مجاز خود دسترسی دارند؟
آیا تمام ورودیهای کاربر اعتبارسنجی شدهاند؟
آیا اطلاعات حساس در محل امن نگهداری میشوند؟
آیا امکان سوءاستفاده از این قابلیت وجود دارد؟
اگر خودم مهاجم بودم، چگونه این بخش را هدف قرار میدادم؟
همین نوع نگاه باعث میشود بسیاری از آسیبپذیریها قبل از انتشار شناسایی شوند.
بهترین روشها
امنیت را بخشی از فرآیند توسعه بدانید.
بررسیهای امنیتی را به صورت دورهای انجام دهید.
دانش خود را درباره حملات جدید بهروز نگه دارید.
اطلاعیههای امنیتی Django را دنبال کنید.
در Code Review درباره امنیت نیز صحبت کنید.
امنترین پروژههای Django متعلق به توسعهدهندگانی نیست که همه قابلیتهای امنیتی را حفظ کردهاند؛ بلکه متعلق به کسانی است که امنیت را در تمام تصمیمهای فنی خود در نظر میگیرند.
جمعبندی
Django یکی از امنترین فریمورکهای توسعه وب در جهان است، اما امنیت آن تنها به خود فریمورک وابسته نیست. بخش عمده آسیبپذیریهای پروژههای Django به دلیل پیکربندی نادرست، اشتباهات برنامهنویسی و رعایت نکردن اصول امنیتی ایجاد میشوند، نه ضعفهای ذاتی Django.
در این مقاله با ۲۰ مورد از رایجترین اشتباهات امنیتی آشنا شدیم؛ از فعال بودن DEBUG=True و افشای SECRET_KEY گرفته تا استفاده ناامن از Raw SQL، اعتماد به ورودی کاربران، کنترل دسترسی ناقص، مدیریت نادرست فایلهای آپلودی و بهروزرسانی نکردن وابستگیها.
خبر خوب این است که تمام این اشتباهات قابل پیشگیری هستند. با رعایت بهترین شیوههای توسعه، بهروزرسانی منظم وابستگیها، اعتبارسنجی دقیق دادهها، استفاده صحیح از قابلیتهای امنیتی Django و بررسی دورهای وضعیت امنیت پروژه، میتوانید احتمال بروز بسیاری از حملات را به حداقل برسانید.
در نهایت، امنیت یک مرحله از توسعه نیست که تنها قبل از انتشار پروژه انجام شود. امنیت یک فرآیند دائمی است که باید از اولین خط کد تا نگهداری و بهروزرسانی نرمافزار ادامه داشته باشد.
بحث و گفتگو(0 نظرات)