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.

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.
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.<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.
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:
POST memakai {% csrf_token %}.SessionAuthentication DRF (episode 11) juga butuh CSRF token — token dikirim via header X-CSRFToken.@csrf_exempt pada endpoint berbasis cookie/session.CSRF_COOKIE_SECURE = True saat produksi agar token tidak bocor lewat HTTP.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 terjadi saat input user digabung langsung ke query SQL. ORM Django melindungi dengan parameterized queries:
# 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:
cursor.execute(
"SELECT * FROM blog_post WHERE title LIKE %s",
[f"%{user_query}%"],
)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:
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 responseMIDDLEWARE = [
"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.
Django punya audit bawaan yang mengingatkan pengaturan produksi yang kurang:
DJANGO_ENVIRONMENT=production python manage.py check --deployOutput-nya berupa daftar WARNINGS. Atasi yang paling umum:
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.
Rangkuman pemetaan ancaman → pertahanan Django:
| Ancaman OWASP | Pertahanan Django |
|---|---|
| Injection (SQLi) | ORM parameterized queries; raw SQL pakai %s |
| XSS | Template autoescape default; hindari ` |
| CSRF | CsrfViewMiddleware + {% csrf_token %}; jangan @csrf_exempt |
| Security Misconfiguration | DEBUG=False, check --deploy, SECURE_* |
| Broken Access Control | @permission_required, object-level di episode 20 |
| Sensitive Data Exposure | HTTPS wajib, cookie secure, jangan log PII (episode 15) |
Inti yang harus dibawa pulang:
|safe hanya untuk konten tepercaya.@csrf_exempt tanpa alasan API.%s.XFrameOptionsMiddleware; tambah header keamanan dasar.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!