Belajar Django - Security Best Practices (OWASP)
Episode 18 of 27

Belajar Django - Security Best Practices (OWASP)

Meng-hardening aplikasi terhadap ancaman OWASP: XSS via autoescape, CSRF protection, SQL injection lewat ORM, clickjacking, security headers, serta audit produksi dengan manage.py check --deploy dan pengaturan SECURE_* untuk HTTPS.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi devblog sekarang kaya fitur — dan semakin kaya, semakin besar permukaan serangannya. Di episode ini kita menoleh ke daftar ancaman paling terkenal di dunia web: OWASP Top 10. Django telah membangun banyak pertahanan bawaan, tetapi pertahanan hanya berguna jika diaktifkan dan dikonfigurasi dengan benar.

Mengapa topik ini penting? Karena mayoritas serangan web berhasil bukan karena teknologi canggih, melainkan karena pengaturan dasar yang salah. DEBUG=True di produksi, CSRF dimatikan, atau HTTPS tidak dipaksa adalah lubang yang bisa dieksploitasi bot dalam hitungan menit. Episode ini menutup lubang-lubang itu.

XSS: Autoescape sebagai Benteng Pertama

Cross-Site Scripting (XSS) terjadi saat input user dirender sebagai HTML mentah. Django memblokirnya secara default: template autoescape (episode 5) mengubah <script> menjadi teks. Ketentuan yang wajib diingat:

  • {{ user_input }} → di-escape, aman.
  • {{ user_input|safe }}tidak di-escape — hanya untuk konten yang kalian yakini.
  • {% autoescape off %} → mematikan escape untuk blok — nyaris tidak pernah diperlukan.
PythonPola aman vs berbahaya
<p>{{ comment.body }}</p>            <!-- aman: di-escape -->
 
<p>{{ comment.body|safe }}</p>       <!-- bahaya jika input user -->

Warning

|safe pada input user adalah kesalahan XSS paling umum. Hanya gunakan |safe untuk konten yang berasal dari developer (misal HTML yang ditulis di admin oleh staf tepercaya). Untuk konten user yang butuh markup, parse dengan library seperti bleach — bukan membiarkan HTML mentah.

Konfirmasi di kode: simpan data mentah di database, biarkan template yang meng-escape saat render. Inilah alasan HttpResponse dengan content yang digabung string adalah anti-pattern — render via template selalu lebih aman.

CSRF: Dari Form sampai API

Cross-Site Request Forgery (CSRF) memaksa browser korban yang sudah login mengirim request berbahaya ke aplikasi kalian. Django melindungi via CsrfViewMiddleware + {% csrf_token %} — sudah kita praktikkan di episode 9.

Aturan yang harus dipatuhi:

  • Semua form POST memakai {% csrf_token %}.
  • SessionAuthentication DRF (episode 11) juga butuh CSRF token — token dikirim via header X-CSRFToken.
  • Jangan pernah @csrf_exempt pada endpoint berbasis cookie/session.
  • CSRF_COOKIE_SECURE = True saat produksi agar token tidak bocor lewat HTTP.
Pythonsettings.py - CSRF hardening
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = False  # wajib False: JS perlu membaca untuk AJAX header
CSRF_TRUSTED_ORIGINS = ["https://devvnull.vercel.app"]

CSRF_TRUSTED_ORIGINS penting saat aplikasi dipasang di balik proxy dengan origin berbeda — tanpa ini, request dari origin yang valid bisa di-403.

SQL Injection: ORM sebagai Perisai

SQL injection terjadi saat input user digabung langsung ke query SQL. ORM Django melindungi dengan parameterized queries:

PythonAman vs tidak aman
# Aman: ORM selalu mem-parameterize nilai
posts = Post.objects.filter(title__icontains=user_query)
 
# TIDAK AMAN: raw SQL dengan string interpolation
from django.db import connection
cursor = connection.cursor()
cursor.execute(f"SELECT * FROM blog_post WHERE title LIKE '%{user_query}%'")

ORM memisahkan SQL dari nilai, sehingga input user diperlakukan sebagai data, bukan perintah. Jika kalian harus memakai raw() atau cursor.execute, gunakan placeholder %s:

PythonRaw SQL yang aman
cursor.execute(
    "SELECT * FROM blog_post WHERE title LIKE %s",
    [f"%{user_query}%"],
)

Clickjacking dan Security Headers

Clickjacking menipu user mengklik elemen yang disembunyikan di atas halaman jahat. Django memblokirnya via XFrameOptionsMiddleware yang mengirim header X-Frame-Options: DENY. Jangan hapus middleware ini dari MIDDLEWARE.

Lengkapi dengan security headers di level web server atau middleware:

Pythondevblog/middleware/security_headers.py
class SecurityHeadersMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
 
    def __call__(self, request):
        response = self.get_response(request)
        response["X-Content-Type-Options"] = "nosniff"
        response["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response["Permissions-Policy"] = "geolocation=(), camera=()"
        return response
Pythonsettings.py - daftarkan middleware
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    ...
    "devblog.middleware.security_headers.SecurityHeadersMiddleware",
]

Header X-Content-Type-Options: nosniff mencegah browser salah menebak tipe file; Referrer-Policy membatasi informasi yang bocor di header Referer. Untuk Content-Security-Policy (CSP), library django-csp lebih lengkap — tetapi mulai dari header dasar di atas dulu.

check --deploy dan SECURE_* Settings

Django punya audit bawaan yang mengingatkan pengaturan produksi yang kurang:

Audit produksi
DJANGO_ENVIRONMENT=production python manage.py check --deploy

Output-nya berupa daftar WARNINGS. Atasi yang paling umum:

Pythonsettings.py - hardening produksi
DEBUG = False
ALLOWED_HOSTS = ["devvnull.vercel.app"]
 
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000          # 1 tahun
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"

Setiap baris punya peran:

  • SECURE_SSL_REDIRECT memaksa HTTPS (via header redirect dari proxy, episode 23).
  • SESSION_COOKIE_SECURE/CSRF_COOKIE_SECURE memastikan cookie hanya dikirim lewat HTTPS.
  • SECURE_HSTS_* memberi tahu browser "selalu pakai HTTPS" — HSTS dicegat browser setelah header pertama, jadi SECURE_HSTS_SECONDS sebaiknya dinaikkan bertahap (300 → 1 tahun).

Warning

SECURE_SSL_REDIRECT dan HSTS bekerja bersama reverse proxy — jika proxy sudah menangani HTTPS, koordinasikan agar redirect tidak dobel. Dan jangan mengaktifkan HSTS di server yang belum stabil HTTPS: browser akan menolak HTTP selamanya selama periode HSTS, dan memblokir akses yang bisa jadi diperlukan.

Checklist Serangan OWASP

Rangkuman pemetaan ancaman → pertahanan Django:

Ancaman OWASPPertahanan Django
Injection (SQLi)ORM parameterized queries; raw SQL pakai %s
XSSTemplate autoescape default; hindari `
CSRFCsrfViewMiddleware + {% csrf_token %}; jangan @csrf_exempt
Security MisconfigurationDEBUG=False, check --deploy, SECURE_*
Broken Access Control@permission_required, object-level di episode 20
Sensitive Data ExposureHTTPS wajib, cookie secure, jangan log PII (episode 15)

Penutup

Inti yang harus dibawa pulang:

  • XSS: autoescape default; |safe hanya untuk konten tepercaya.
  • CSRF: token di semua POST; jangan @csrf_exempt tanpa alasan API.
  • SQLi: ORM aman; raw SQL wajib placeholder %s.
  • Clickjacking: jaga XFrameOptionsMiddleware; tambah header keamanan dasar.
  • Jalankan manage.py check --deploy sebagai checklist produksi.
  • SECURE_* + HSTS mengamankan transport; koordinasikan dengan proxy di episode 23.

Di episode 19 selanjutnya kita melindungi API publik: Rate Limiting & Throttling — DRF throttle classes, batas per-user dan per-IP, custom throttles, serta melindungi endpoint publik blog dari abuse dan brute-force. Sampai jumpa di episode 19!

Belajar Django - Security Best Practices (OWASP) | Belajar Django